KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
2023-09-04 20:58:28 +07:00

254 lines
7.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.client
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.TileLayerList
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.world.*
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Matrix4fStack
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f
import java.io.Closeable
import java.util.LinkedList
/**
* Псевдо zPos у фоновых тайлов
*
* Добавление этого числа к zPos гарантирует, что фоновые тайлы будут отрисованы
* первыми (на самом дальнем плане)
*/
const val Z_LEVEL_BACKGROUND = 60000
const val Z_LEVEL_LIQUID = 10000
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
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 = true
fun bake() {
if (!isDirty) return
isDirty = false
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
bakedMeshes.clear()
layers.clear()
for ((pos, tile) in view.iterateTiles()) {
if (!world.chunkMap.inBounds(this@ClientChunk.pos.tile + pos)) continue
val material = tile.material
if (material != null) {
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, pos, background = isBackground)
}
val modifier = tile.modifier
if (modifier != null) {
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, pos, 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() {
super.foregroundChanges()
foregroundRenderer.isDirty = true
forEachNeighbour {
it.foregroundRenderer.isDirty = true
}
}
override fun backgroundChanges() {
super.backgroundChanges()
backgroundRenderer.isDirty = true
forEachNeighbour {
it.backgroundRenderer.isDirty = true
}
}
/**
* Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо,
* и загружает её в видеопамять.
*
* Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке)
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
*
*/
fun bake() {
backgroundRenderer.bake()
foregroundRenderer.bake()
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 = pos.tile.toFloatVector()) {
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()
}
}
}
}
private val entityRenderers = HashMap<Entity, EntityRenderer>()
override fun onEntityAdded(entity: Entity) {
entityRenderers[entity] = EntityRenderer.getRender(world.client, entity, this)
}
override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) {
val renderer = otherChunk.entityRenderers[entity] ?: throw IllegalStateException("$otherChunk has no renderer for $entity!")
entityRenderers[entity] = renderer
renderer.chunk = this
}
override fun onEntityTransferedFromThis(entity: Entity, otherChunk: ClientChunk) {
entityRenderers.remove(entity)
}
override fun onEntityRemoved(entity: Entity) {
entityRenderers.remove(entity)!!.close()
}
override fun close() {
backgroundRenderer.close()
foregroundRenderer.close()
for (renderer in entityRenderers.values) {
renderer.close()
}
}
}