KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt
2023-09-03 13:48:40 +07:00

461 lines
14 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 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<ClientWorld, ClientChunk>(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<Pair<ConfiguredStaticMesh, Int>>()
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<ShadowGeometryTracker>()
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<LiquidDefinition>()
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<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()
}
}
companion object {
const val SHADOW_GEOMETRY_SUBDIVISION = 4
const val SHADOW_GEOMETRY_SQUARE_SIZE = CHUNK_SIZE / SHADOW_GEOMETRY_SUBDIVISION
}
}