package ru.dbotthepony.kstarbound.client import org.lwjgl.opengl.GL11.GL_LINES import org.lwjgl.opengl.GL11.GL_TRIANGLES import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.shader.GLHardLightGeometryProgram import ru.dbotthepony.kstarbound.client.gl.shader.GLSoftLightGeometryProgram import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer import ru.dbotthepony.kstarbound.client.render.GPULightRenderer 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.ITileChunk import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.arrays.Matrix4f import ru.dbotthepony.kvector.util2d.intersectCircleRectangle import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector3f import java.io.Closeable import java.util.LinkedList import java.util.function.IntSupplier /** * Псевдо 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 layerChangeset: IntSupplier, private val view: () -> ITileChunk, private val isBackground: Boolean) : AutoCloseable { private val layers = TileLayerList() val bakedMeshes = LinkedList>() private var changeset = -1 fun bake() { val view = view() if (state.isSameThread()) { for (mesh in bakedMeshes) { mesh.first.close() } } bakedMeshes.clear() layers.clear() for ((pos, tile) in view.iterate()) { 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 loadRenderers() { val view = view() for ((_, tile) in view.iterate()) { val material = tile.material val modifier = tile.modifier if (material != null) { world.client.tileRenderers.getTileRenderer(material.materialName) } if (modifier != null) { world.client.tileRenderers.getModifierRenderer(modifier.modName) } } } fun uploadStatic(clear: Boolean = true) { for ((baked, builder, zLevel) in layers.buildSortedLayerList()) { bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel) } if (clear) { layers.clear() } } fun render(stack: Matrix4fStack) { val transform = stack.last() for (mesh in bakedMeshes) { mesh.first.render(transform) } } fun autoBake() { if (changeset != layerChangeset.asInt) { this.bake() changeset = layerChangeset.asInt } } fun autoUpload() { 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() } } fun autoBakeAndUpload() { autoBake() autoUpload() } fun bakeAndRender(transform: Matrix4fStack) { autoBakeAndUpload() render(transform) } override fun close() { for (mesh in bakedMeshes) { mesh.first.close() } } } val debugCollisions get() = world.client.settings.debugCollisions val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd) private val foregroundRenderer = TileLayerRenderer(::foregroundChangeset, { world.getView(pos).foregroundView }, isBackground = false) private val backgroundRenderer = TileLayerRenderer(::backgroundChangeset, { world.getView(pos).backgroundView }, isBackground = true) /** * Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее) * * Вызывается перед tesselateStatic() */ fun loadRenderers() { foregroundRenderer.loadRenderers() backgroundRenderer.loadRenderers() } /** * Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо, * и загружает её в видеопамять. * * Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке) * но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась * */ fun bake() { backgroundRenderer.autoBake() foregroundRenderer.autoBake() if (state.isSameThread()) upload() } /** * Загружает в видеопамять всю геометрию напрямую, если есть что загружать */ fun upload() { backgroundRenderer.autoUpload() foregroundRenderer.autoUpload() } private inner class ShadowGeometryTracker(val x: Int, val y: Int) : GPULightRenderer.ShadowGeometryRenderer { private val hardShadowGeometry by lazy(LazyThreadSafetyMode.NONE) { StreamVertexBuilder(state, GPULightRenderer.SHADOW_FORMAT, GeometryType.LINES) } private var hardShadowGeometryRev = -1 private val softShadowGeometry by lazy(LazyThreadSafetyMode.NONE) { StreamVertexBuilder(state, GPULightRenderer.SHADOW_FORMAT_SOFT, GeometryType.QUADS_ALTERNATIVE) } private var softShadowGeometryRev = -1 private fun buildGeometry(builder: StreamVertexBuilder, line: (StreamVertexBuilder, Float, Float, Float, Float) -> Unit) { builder.builder.begin() for (x in this.x * SHADOW_GEOMETRY_SQUARE_SIZE until (this.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) { for (y in this.y * SHADOW_GEOMETRY_SQUARE_SIZE until (this.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) { if (foregroundView.getTile(x, y).material?.renderParameters?.lightTransparent == false) { if (x == 0 || foregroundView.getTile(x - 1, y).material?.renderParameters?.lightTransparent != true) { line(builder, x.toFloat(), y + 1f, x.toFloat(), y.toFloat()) } if (x == CHUNK_SIZE - 1 || foregroundView.getTile(x + 1, y).material?.renderParameters?.lightTransparent != true) { line(builder, x + 1f, y.toFloat(), x + 1f, y + 1f) } if (y == 0 || foregroundView.getTile(x, y - 1).material?.renderParameters?.lightTransparent != true) { line(builder, x.toFloat(), y.toFloat(), x + 1f, y.toFloat()) } if (y == CHUNK_SIZE - 1 || foregroundView.getTile(x, y + 1).material?.renderParameters?.lightTransparent != true) { line(builder, x + 1f, y + 1f, x.toFloat(), y + 1f) } } } } builder.upload() } fun buildHardGeometry() { hardShadowGeometryRev = tileChangeset buildGeometry(hardShadowGeometry) { it, x0, y0, x1, y1 -> it.builder.vertex().pushVec2f(x0, y0) it.builder.vertex().pushVec2f(x1, y1) } } fun buildSoftGeometry() { softShadowGeometryRev = tileChangeset buildGeometry(softShadowGeometry) { it, x0, y0, x1, y1 -> it.builder.shadowLine(x0, y0, x1, y1) } } override fun renderHardGeometry( renderer: GPULightRenderer, lightPosition: Vector2f, lightRadius: Float, stack: Matrix4fStack, program: GLHardLightGeometryProgram ) { if (hardShadowGeometryRev != tileChangeset) { buildHardGeometry() } hardShadowGeometry.draw(GL_LINES) } override fun renderSoftGeometry( renderer: GPULightRenderer, lightPosition: Vector2f, lightRadius: Float, stack: Matrix4fStack, program: GLSoftLightGeometryProgram ) { if (softShadowGeometryRev != tileChangeset) { buildSoftGeometry() } softShadowGeometry.draw(GL_TRIANGLES) } } private val shadowGeometry = ArrayList() init { for (x in 0 until SHADOW_GEOMETRY_SUBDIVISION) { for (y in 0 until SHADOW_GEOMETRY_SUBDIVISION) { shadowGeometry.add(ShadowGeometryTracker(x, y)) } } } inner class Renderer(val renderOrigin: ChunkPos = pos) : GPULightRenderer.ShadowGeometryRenderer { override fun renderHardGeometry( renderer: GPULightRenderer, lightPosition: Vector2f, lightRadius: Float, stack: Matrix4fStack, program: GLHardLightGeometryProgram ) { if (!intersectCircleRectangle(lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf, (renderOrigin.x + 1) * CHUNK_SIZEf, (renderOrigin.y + 1) * CHUNK_SIZEf)) { return } var setOnce = false for (geometry in shadowGeometry) { if (intersectCircleRectangle( lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) ) { if (!setOnce) { program.localToWorldTransform = Matrix4f.identity().translateWithMultiplication( Vector3f(x = renderOrigin.x * CHUNK_SIZEf, y = renderOrigin.y * CHUNK_SIZEf )) setOnce = true } geometry.renderHardGeometry(renderer, lightPosition, lightRadius, stack, program) } } } override fun renderSoftGeometry( renderer: GPULightRenderer, lightPosition: Vector2f, lightRadius: Float, stack: Matrix4fStack, program: GLSoftLightGeometryProgram ) { if (!intersectCircleRectangle(lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf, (renderOrigin.x + 1) * CHUNK_SIZEf, (renderOrigin.y + 1) * CHUNK_SIZEf)) { return } var setOnce = false for (geometry in shadowGeometry) { if (intersectCircleRectangle( lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE, renderOrigin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) ) { if (!setOnce) { program.localToWorldTransform = Matrix4f.identity().translateWithMultiplication( Vector3f(x = renderOrigin.x * CHUNK_SIZEf, y = renderOrigin.y * CHUNK_SIZEf )) setOnce = true } geometry.renderSoftGeometry(renderer, lightPosition, lightRadius, stack, program) } } } fun addLayers(layers: LayeredRenderer) { for ((baked, zLevel) in backgroundRenderer.bakedMeshes) { layers.add(zLevel + Z_LEVEL_BACKGROUND) { it.push().last().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf) baked.renderStacked(it) it.pop() } } for ((baked, zLevel) in foregroundRenderer.bakedMeshes) { layers.add(zLevel) { it.push().last().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf) 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 * CHUNK_SIZEf + relative.x.toFloat(), renderOrigin.y * CHUNK_SIZEf + relative.y.toFloat()) renderer.render(it) it.pop() } } layers.add(Z_LEVEL_LIQUID) { it.push().last().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf) val types = ArrayList() for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { val state = getCell(x, y) if (state.liquid.def != null && !types.any { it === state.liquid.def }) { types.add(state.liquid.def!!) } } } 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() } } companion object { const val SHADOW_GEOMETRY_SUBDIVISION = 4 const val SHADOW_GEOMETRY_SQUARE_SIZE = CHUNK_SIZE / SHADOW_GEOMETRY_SUBDIVISION } }