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.program.GLHardLightGeometryProgram import ru.dbotthepony.kstarbound.client.gl.program.GLSoftLightGeometryProgram import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.StatefulVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.quad import ru.dbotthepony.kstarbound.client.gl.vertex.shadowLine import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer import ru.dbotthepony.kstarbound.client.render.GPULightRenderer import ru.dbotthepony.kstarbound.client.render.TileLayerList import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.world.* import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f import ru.dbotthepony.kvector.util2d.intersectCircleRectangle import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.nfloat.Vector2f import ru.dbotthepony.kvector.vector.nfloat.Vector3f 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 layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable { private val layers = TileLayerList() val bakedMeshes = LinkedList>() private var changeset = -1 fun bake(view: ITileChunk) { if (state.isSameThread()) { for (mesh in bakedMeshes) { mesh.first.close() } } bakedMeshes.clear() layers.clear() for ((pos, tile) in view.posToTile) { 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(view: ITileChunk) { for ((_, tile) in view.posToTile) { 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(provider: () -> ITileChunk) { if (changeset != layerChangeset.invoke()) { this.bake(provider.invoke()) changeset = layerChangeset.invoke() } } 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(provider: () -> ITileChunk) { autoBake(provider) autoUpload() } fun bakeAndRender(transform: Matrix4fStack, provider: () -> ITileChunk) { autoBakeAndUpload(provider) 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(foreground::changeset, isBackground = false) private val backgroundRenderer = TileLayerRenderer(background::changeset, isBackground = true) private fun getForegroundView(): ITileChunk { return world.getForegroundView(pos)!! } private fun getBackgroundView(): ITileChunk { return world.getBackgroundView(pos)!! } /** * Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее) * * Вызывается перед tesselateStatic() */ fun loadRenderers() { foregroundRenderer.loadRenderers(getForegroundView()) backgroundRenderer.loadRenderers(getBackgroundView()) } /** * Отрисовывает всю геометрию напрямую */ fun render(stack: Matrix4fStack) { backgroundRenderer.render(stack) foregroundRenderer.render(stack) } /** * Отрисовывает всю геометрию напрямую, с проверкой, изменился ли чанк */ fun bakeAndRender(stack: Matrix4fStack) { backgroundRenderer.bakeAndRender(stack, this::getBackgroundView) foregroundRenderer.bakeAndRender(stack, this::getForegroundView) } /** * Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо, * и загружает её в видеопамять. * * Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке) * но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась * */ fun bake() { backgroundRenderer.autoBake(this::getBackgroundView) foregroundRenderer.autoBake(this::getForegroundView) 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) { StatefulVertexBuilder(state, GPULightRenderer.SHADOW_FORMAT, GeometryType.LINES) } private var hardShadowGeometryRev = -1 private val softShadowGeometry by lazy(LazyThreadSafetyMode.NONE) { StatefulVertexBuilder(state, GPULightRenderer.SHADOW_FORMAT_SOFT, GeometryType.QUADS_ALTERNATIVE) } private var softShadowGeometryRev = -1 private fun buildGeometry(builder: StatefulVertexBuilder, line: (StatefulVertexBuilder, Float, Float, Float, Float) -> Unit) { 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 (foreground[x, y].material?.renderParameters?.lightTransparent == false) { if (x == 0 || foreground[x - 1, y].material?.renderParameters?.lightTransparent != true) { line(builder, x.toFloat(), y + 1f, x.toFloat(), y.toFloat()) } if (x == CHUNK_SIZE - 1 || foreground[x + 1, y].material?.renderParameters?.lightTransparent != true) { line(builder, x + 1f, y.toFloat(), x + 1f, y + 1f) } if (y == 0 || foreground[x, y - 1].material?.renderParameters?.lightTransparent != true) { line(builder, x.toFloat(), y.toFloat(), x + 1f, y.toFloat()) } if (y == CHUNK_SIZE - 1 || foreground[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.vertex().pushVec2f(x0, y0) it.vertex().pushVec2f(x1, y1) } } fun buildSoftGeometry() { softShadowGeometryRev = tileChangeset buildGeometry(softShadowGeometry, StatefulVertexBuilder::shadowLine) } 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 OneShotRenderer constructor(val origin: ChunkPos = pos) : ILayeredRenderer, GPULightRenderer.ShadowGeometryRenderer { private val layerQueue = ArrayDeque Unit, Int>>() override fun renderHardGeometry( renderer: GPULightRenderer, lightPosition: Vector2f, lightRadius: Float, stack: Matrix4fStack, program: GLHardLightGeometryProgram ) { if (!intersectCircleRectangle(lightPosition, lightRadius, origin.x * CHUNK_SIZEf, origin.y * CHUNK_SIZEf, (origin.x + 1) * CHUNK_SIZEf, (origin.y + 1) * CHUNK_SIZEf)) { return } var setOnce = false for (geometry in shadowGeometry) { if (intersectCircleRectangle( lightPosition, lightRadius, origin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE, origin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE, origin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE, origin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) ) { if (!setOnce) { program.localToWorldTransform.set( Matrix4f.IDENTITY.translateWithMultiplication( Vector3f(x = origin.x * CHUNK_SIZEf, y = origin.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, origin.x * CHUNK_SIZEf, origin.y * CHUNK_SIZEf, (origin.x + 1) * CHUNK_SIZEf, (origin.y + 1) * CHUNK_SIZEf)) { return } var setOnce = false for (geometry in shadowGeometry) { if (intersectCircleRectangle( lightPosition, lightRadius, origin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE, origin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE, origin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE, origin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) ) { if (!setOnce) { program.localToWorldTransform.set( Matrix4f.IDENTITY.translateWithMultiplication( Vector3f(x = origin.x * CHUNK_SIZEf, y = origin.y * CHUNK_SIZEf))) setOnce = true } geometry.renderSoftGeometry(renderer, lightPosition, lightRadius, stack, program) } } } init { for ((baked, zLevel) in backgroundRenderer.bakedMeshes) { layerQueue.add(baked::renderStacked to (zLevel + Z_LEVEL_BACKGROUND)) } for ((baked, zLevel) in foregroundRenderer.bakedMeshes) { layerQueue.add(baked::renderStacked to zLevel) } for (renderer in entityRenderers.values) { layerQueue.add(lambda@{ it: Matrix4fStack -> val relative = renderer.renderPos - posVector2d it.push().translateWithMultiplication(relative.x.toFloat(), relative.y.toFloat()) renderer.render(it) it.pop() Unit } to renderer.layer) } layerQueue.add({ it: Matrix4fStack -> val types = ArrayList() for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { val state = getLiquid(x, y) if (state != null && !types.any { it === state.def }) { types.add(state.def) } } } val program = state.programs.liquid program.use() program.transform.set(it.last) val builder = program.builder for (type in types) { builder.begin() for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { val state = getLiquid(x, y) if (state != null && state.def === type) { builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.level) } } } program.baselineColor.set(type.color) builder.upload() builder.draw() } } to Z_LEVEL_LIQUID) layerQueue.sortBy { return@sortBy it.second } } override fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int { if (layerQueue.isEmpty()) return Int.MIN_VALUE stack.push().translateWithMultiplication(x = origin.x * CHUNK_SIZEf, y = origin.y * CHUNK_SIZEf) var pair = layerQueue.last() while (pair.second >= zPos) { pair.first.invoke(stack) layerQueue.removeLast() if (layerQueue.isEmpty()) { stack.pop() return Int.MIN_VALUE } pair = layerQueue.last() } stack.pop() return layerQueue.last().second } override fun bottomMostZLevel(): Int { if (layerQueue.isEmpty()) { return Int.MIN_VALUE } return layerQueue.last().second } } private val entityRenderers = HashMap() override fun onEntityAdded(entity: Entity) { entityRenderers[entity] = EntityRenderer.getRender(state, 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 } }