KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/render/TileRenderer.kt

276 lines
8.7 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.render
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.TileDefinition
import ru.dbotthepony.kstarbound.defs.TileRenderMatchPiece
import ru.dbotthepony.kstarbound.defs.TileRenderPiece
import ru.dbotthepony.kstarbound.gl.*
import ru.dbotthepony.kstarbound.math.Vector2i
import ru.dbotthepony.kstarbound.util.Color
import ru.dbotthepony.kstarbound.world.ITileChunk
import kotlin.collections.HashMap
data class TileLayer(
val bakedProgramState: BakedProgramState,
val vertexBuilder: VertexBuilder,
val zPos: Int)
class TileLayerList {
private val layers = HashMap<BakedProgramState, ArrayList<TileLayer>>()
fun getLayer(programState: BakedProgramState, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
val list = layers.computeIfAbsent(programState) {ArrayList()}
for (layer in list) {
if (layer.zPos == zLevel) {
return layer.vertexBuilder
}
}
val computed = TileLayer(programState, compute.invoke(), zLevel)
list.add(computed)
return computed.vertexBuilder
}
fun buildList(): List<TileLayer> {
val list = ArrayList<TileLayer>()
for (getList in layers.values) {
list.addAll(getList)
}
list.sortBy {
// унарный минус для инвентирования порядка (сначала маленькие, потом большие)
return@sortBy -it.zPos
}
return list
}
fun clear() = layers.clear()
}
class TileRenderers(val state: GLStateTracker) {
private val foregroundTilePrograms = HashMap<GLTexture2D, ForegroundTileProgram>()
private val backgroundTilePrograms = HashMap<GLTexture2D, BackgroundTileProgram>()
private val tileRenderers = HashMap<String, TileRenderer>()
operator fun get(tile: String): TileRenderer {
return tileRenderers.computeIfAbsent(tile) {
val def = Starbound.getTileDefinition(tile) // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(state, def!!)
}
}
private inner class ForegroundTileProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTexture) {
override fun setup() {
super.setup()
state.activeTexture = 0
state.depthTest = false
program["_texture"] = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other is ForegroundTileProgram) {
return texture == other.texture
}
return super.equals(other)
}
override fun hashCode(): Int {
return texture.hashCode()
}
}
private inner class BackgroundTileProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTextureColor) {
override fun setup() {
super.setup()
state.activeTexture = 0
state.depthTest = false
program["_texture"] = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
program["_color"] = BACKGROUND_COLOR
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other is BackgroundTileProgram) {
return texture == other.texture
}
return super.equals(other)
}
override fun hashCode(): Int {
return texture.hashCode()
}
}
/**
* Возвращает запечённое состояние shaderVertexTexture с данной текстурой
*/
fun foreground(texture: GLTexture2D): BakedProgramState {
return foregroundTilePrograms.computeIfAbsent(texture, ::ForegroundTileProgram)
}
/**
* Возвращает запечённое состояние shaderVertexTextureColor с данной текстурой
*/
fun background(texture: GLTexture2D): BakedProgramState {
return backgroundTilePrograms.computeIfAbsent(texture, ::BackgroundTileProgram)
}
companion object {
val BACKGROUND_COLOR = Color(0.4f, 0.4f, 0.4f)
}
}
private enum class TileRenderTesselateResult {
NO_MATCH,
CONTINUE,
HALT
}
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
val texture = state.loadNamedTexture(tile.render.texture).also {
it.textureMagFilter = GL_NEAREST
}
val bakedProgramState = state.tileRenderers.foreground(texture)
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
// private var notifiedDepth = false
private fun tesselateAt(piece: TileRenderPiece, getter: ITileChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) {
val fx = pos.x.toFloat()
val fy = pos.y.toFloat()
var a = fx
var b = fy
var c = fx + piece.textureSize.x / BASELINE_TEXTURE_SIZE
var d = fy + piece.textureSize.y / BASELINE_TEXTURE_SIZE
if (offset != Vector2i.ZERO) {
a += offset.x / BASELINE_TEXTURE_SIZE
// в json файлах y указан как положительный вверх
b += offset.y / BASELINE_TEXTURE_SIZE
c += offset.x / BASELINE_TEXTURE_SIZE
d += offset.y / BASELINE_TEXTURE_SIZE
}
/*
if (!notifiedDepth && tile.render.zLevel >= 5900) {
LOGGER.warn("Tile {} has out of bounds zLevel of {}", tile.materialName, tile.render.zLevel)
notifiedDepth = true
}
*/
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
//builder.quadZ(a, b, c, d, tile.render.zLevel.toFloat() + 200f, VertexTransformers.uv(u0, v1, u1, v0))
builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
} else {
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt()
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
//builder.quadZ(a, b, c, d, tile.render.zLevel.toFloat() + 200f, VertexTransformers.uv(u0, v1, u1, v0))
builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
}
}
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: VertexBuilder, background: Boolean): TileRenderTesselateResult {
if (matchPiece.test(getter, tile, pos)) {
for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) {
val program: BakedProgramState
if (background) {
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture))
} else {
program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture))
}
tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.render.zLevel) {
return@getLayer VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}, pos, renderPiece.offset)
} else {
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
}
}
for (subPiece in matchPiece.subMatches) {
val matched = tesselatePiece(subPiece, getter, layers, pos, thisBuilder, background)
if (matched == TileRenderTesselateResult.HALT || matched == TileRenderTesselateResult.CONTINUE && matchPiece.haltOnSubMatch) {
return TileRenderTesselateResult.HALT
}
}
if (matchPiece.haltOnMatch) {
return TileRenderTesselateResult.HALT
}
return TileRenderTesselateResult.CONTINUE
}
return TileRenderTesselateResult.NO_MATCH
}
/**
* Тесселирует тайлы в заданной позиции в нужном билдере
*
* [getter] Нужен для получения информации о ближайших блоках
*
* [layers] содержит текущие программы и их билдеры и их zPos
*
* Тесселирует тайлы в границы -1f .. CHUNK_SIZEf + 1f на основе [pos]
*/
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
tile.render.renderTemplate ?: return
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.render.zLevel) {
return@getLayer VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}
for ((_, matcher) in tile.render.renderTemplate.matches) {
for (matchPiece in matcher.pieces) {
val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background)
if (matched == TileRenderTesselateResult.HALT) {
break
}
}
}
}
companion object {
const val BASELINE_TEXTURE_SIZE = 8f
const val Z_LEVEL = 10f
private val LOGGER = LogManager.getLogger()
}
}