254 lines
7.2 KiB
Kotlin
254 lines
7.2 KiB
Kotlin
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()
|
||
}
|
||
}
|
||
}
|