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

281 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.client.render
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.defs.tile.RenderMatch
import ru.dbotthepony.kstarbound.defs.tile.RenderPiece
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.nint.Vector2i
import kotlin.collections.HashMap
data class TileLayer(
val bakedProgramState: BakedProgramState,
val vertexBuilder: DynamicVertexBuilder,
val zPos: Int)
class TileLayerList {
private val layers = HashMap<BakedProgramState, ArrayList<TileLayer>>()
fun getLayer(programState: BakedProgramState, zLevel: Int, compute: () -> DynamicVertexBuilder): DynamicVertexBuilder {
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()
val isEmpty get() = layers.isEmpty()
val isNotEmpty get() = layers.isNotEmpty()
}
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.renderParameters.absoluteTexturePath).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: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, 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 / PIXELS_IN_STARBOUND_UNITf
var d = fy + piece.textureSize.y / PIXELS_IN_STARBOUND_UNITf
if (offset != Vector2i.ZERO) {
a += offset.x / PIXELS_IN_STARBOUND_UNITf
// в json файлах Y указан как положительный вверх,
// что соответствует нашему миру
b += offset.y / PIXELS_IN_STARBOUND_UNITf
c += offset.x / PIXELS_IN_STARBOUND_UNITf
d += offset.y / PIXELS_IN_STARBOUND_UNITf
}
if (tile.renderParameters.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,
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
} else {
val variant = (getter.randomDoubleFor(pos) * tile.renderParameters.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,
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
}
}
private fun tesselatePiece(matchPiece: RenderMatch, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, 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.renderParameters.zLevel) {
return@getLayer DynamicVertexBuilder(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
*
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
val template = tile.renderTemplate
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.renderParameters.zLevel) {
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}
for ((_, matcher) in template.matches) {
for (matchPiece in matcher) {
val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background)
if (matched == TileRenderTesselateResult.HALT) {
break
}
}
}
}
companion object {
const val Z_LEVEL = 10f
private val LOGGER = LogManager.getLogger()
}
}