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(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>() 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() private var liquidTypesVer = 0 private fun getLiquidTypes(): Collection { 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() 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() } } }