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

231 lines
8.1 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.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.client.gl.shader.GLTileProgram
import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.arrays.Matrix4f
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.HashMap
/**
* Хранит в себе программы для отрисовки определённых [TileDefinition]
*
* Создаётся единожды как потомок [GLStateTracker]
*/
class TileRenderers(val client: StarboundClient) {
val state get() = client.gl
private val foreground = HashMap<GLTexture2D, Config>()
private val background = HashMap<GLTexture2D, Config>()
private val matCache = HashMap<String, TileRenderer>()
private val modCache = HashMap<String, TileRenderer>()
fun getMaterialRenderer(defName: String): TileRenderer {
return matCache.computeIfAbsent(defName) {
val def = Starbound.tiles[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(this, def!!.value)
}
}
fun getModifierRenderer(defName: String): TileRenderer {
return modCache.computeIfAbsent(defName) {
val def = Starbound.tileModifiers[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(this, def!!.value)
}
}
private inner class Config(private val texture: GLTexture2D, private val color: RGBAColor) : RenderConfig<GLTileProgram>(state.programs.tile) {
override val initialBuilderCapacity: Int
get() = 1024
override fun setup(transform: Matrix4f) {
super.setup(transform)
state.activeTexture = 0
state.depthTest = false
program.texture = 0
texture.bind()
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST
program.transform = transform
program.color = color
}
override fun uninstall() {}
}
fun foreground(texture: GLTexture2D): RenderConfig<GLTileProgram> {
return foreground.computeIfAbsent(texture) { Config(it, FOREGROUND_COLOR) }
}
fun background(texture: GLTexture2D): RenderConfig<GLTileProgram> {
return background.computeIfAbsent(texture) { Config(it, BACKGROUND_COLOR) }
}
companion object {
val BACKGROUND_COLOR = RGBAColor(0.4f, 0.4f, 0.4f)
val FOREGROUND_COLOR = RGBAColor(1f, 1f, 1f)
}
}
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift
}
}
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
return otherTile?.modifier == definition
}
}
class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
private enum class TestResult {
NO_MATCH,
CONTINUE,
HALT
}
val state get() = renderers.state
val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
val equalityTester: EqualityRuleTester = when (def) {
is TileDefinition -> TileEqualityTester(def)
is MaterialModifier -> ModifierEqualityTester(def)
else -> throw IllegalStateException()
}
val bakedProgramState = texture?.let { renderers.foreground(it) }
val bakedBackgroundProgramState = texture?.let { renderers.background(it) }
// private var notifiedDepth = false
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
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
}
var mins = piece.texturePosition
var maxs = piece.texturePosition + piece.textureSize
if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.texture == null) {
val variant = (getter.randomDoubleFor(pos) * def.renderParameters.variants).toInt()
mins += piece.variantStride * variant
maxs += piece.variantStride * variant
}
if (def.renderParameters.multiColored && piece.colorStride != null && self.color != TileColor.DEFAULT) {
mins += piece.colorStride * self.color.ordinal
maxs += piece.colorStride * self.color.ordinal
}
val (u0, v0) = texture!!.pixelToUV(mins)
val (u1, v1) = texture.pixelToUV(maxs)
builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (isModifier) self.modifierHueShift else self.hueShift) })
}
private fun tesselatePiece(
self: ITileState,
matchPiece: RenderMatch,
getter: ITileAccess,
meshBuilder: MultiMeshBuilder,
pos: Vector2i,
thisBuilder: VertexBuilder,
background: Boolean,
isModifier: Boolean,
): TestResult {
if (matchPiece.test(getter, equalityTester, pos)) {
for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) {
val program = if (background) {
renderers.background(state.loadTexture(renderPiece.piece.texture!!))
} else {
renderers.foreground(state.loadTexture(renderPiece.piece.texture!!))
}
tesselateAt(self, renderPiece.piece, getter, meshBuilder.get(program, def.renderParameters.zLevel).mode(GeometryType.QUADS), pos, renderPiece.offset, isModifier)
} else {
tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier)
}
}
for (subPiece in matchPiece.subMatches) {
val matched = tesselatePiece(self, subPiece, getter, meshBuilder, pos, thisBuilder, background, isModifier)
if (matched == TestResult.HALT || matched == TestResult.CONTINUE && matchPiece.haltOnSubMatch) {
return TestResult.HALT
}
}
if (matchPiece.haltOnMatch) {
return TestResult.HALT
}
return TestResult.CONTINUE
}
return TestResult.NO_MATCH
}
/**
* Тесселирует тайлы в заданной позиции в нужном билдере
*
* [getter] Нужен для получения информации о ближайших блоках
*
* [meshBuilder] содержит текущие программы и их билдеры и их zPos
*
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
if (texture == null) return
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
val template = def.renderTemplate.value ?: return
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState!! else bakedProgramState!!, def.renderParameters.zLevel).mode(GeometryType.QUADS)
for ((_, matcher) in template.matches) {
for (matchPiece in matcher) {
val matched = tesselatePiece(self, matchPiece, getter, meshBuilder, pos, vertexBuilder, background, isModifier)
if (matched == TestResult.HALT) {
break
}
}
}
}
companion object {
const val Z_LEVEL = 10f
private val LOGGER = LogManager.getLogger()
}
}