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

245 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.render
import org.lwjgl.glfw.GLFW.glfwGetTime
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.TileRenderMatchedPiece
import ru.dbotthepony.kstarbound.defs.TileRenderPiece
import ru.dbotthepony.kstarbound.gl.*
import ru.dbotthepony.kstarbound.math.Matrix4f
import ru.dbotthepony.kstarbound.math.Vector2i
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
import ru.dbotthepony.kstarbound.world.IChunk
import kotlin.collections.HashMap
class TileRenderers(val state: GLStateTracker) {
private val simpleBakedPrograms = HashMap<GLTexture2D, SimpleBakedProgram>()
private val tileRenderers = HashMap<String, TileRenderer>()
operator fun get(tile: String): TileRenderer {
return tileRenderers.computeIfAbsent(tile) {
return@computeIfAbsent TileRenderer(state, Starbound.loadTileDefinition(tile))
}
}
private inner class SimpleBakedProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTexture) {
override fun setup() {
super.setup()
state.activeTexture = 0
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 SimpleBakedProgram) {
return texture == other.texture
}
return super.equals(other)
}
override fun hashCode(): Int {
return texture.hashCode()
}
}
/**
* Возвращает запечённое состояние shaderVertexTexture с данной текстурой
*/
fun simpleProgram(texture: GLTexture2D): BakedProgramState {
return simpleBakedPrograms.computeIfAbsent(texture, ::SimpleBakedProgram)
}
}
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
val texture = state.loadNamedTexture(tile.render.texture).also {
it.textureMagFilter = GL_NEAREST
}
val bakedProgramState = state.tileRenderers.simpleProgram(texture)
private fun tesselateAt(piece: TileRenderPiece, getter: IChunk, 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 (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, 1f, 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, 1f, VertexTransformers.uv(u0, v1, u1, v0))
}
}
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: IChunk, builders: MutableMap<BakedProgramState, VertexBuilder>, pos: Vector2i, thisBuilder: VertexBuilder) {
if (matchPiece.test(getter, tile, pos)) {
for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) {
tesselateAt(renderPiece.piece, getter, builders.computeIfAbsent(state.tileRenderers.simpleProgram(state.loadNamedTexture(renderPiece.piece.texture))) {
return@computeIfAbsent VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}, pos, renderPiece.offset)
} else {
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
}
}
for (subPiece in matchPiece.subMatches) {
tesselatePiece(subPiece, getter, builders, pos, thisBuilder)
}
}
}
/**
* Тесселирует тайлы в заданной позиции в нужном билдере
*
* [getter] Нужен для получения информации о ближайших блоках
*
* [builders] содержит текущие программы и их билдеры
*
* Тесселирует тайлы в границы -1f .. CHUNK_SIZEf + 1f на основе [pos]
*/
fun tesselate(getter: IChunk, builders: MutableMap<BakedProgramState, VertexBuilder>, pos: Vector2i) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
tile.render.renderTemplate ?: return
val builder = builders.computeIfAbsent(bakedProgramState) {
return@computeIfAbsent VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}
for ((_, matcher) in tile.render.renderTemplate.matches) {
for (matchPiece in matcher.pieces) {
tesselatePiece(matchPiece, getter, builders, pos, builder)
}
}
/*
val fx = pos.x.toFloat()
val fy = pos.y.toFloat()
val a = fx
val b = fy
val c = fx + 1f
val d = fy + 1f
val piece = tile.render.renderTemplate.pieces[tile.render.renderTemplate.representativePiece]!!
if (tile.render.variants == 0) {
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
builder.quadZ(b, a, d, c, 1f, VertexTransformers.uv(u0, v0, u1, v1))
} 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(b, a, d, c, 1f, VertexTransformers.uv(u0, v0, u1, v1))
}
*/
}
fun renderPiece() {
val vao = state.newVAO()
val vbo = state.newVBO()
val ebo = state.newEBO()
vao.bind()
vbo.bind()
val base = tile.render.renderTemplate!!.pieces["base"]!!
val builder = GLFlatAttributeListBuilder.VERTEX_TEXTURE.vertexBuilder(VertexType.QUADS)
run {
val pos1 = base.texturePosition + base.variantStride!! * (glfwGetTime() % 15).toInt()
val pos2 = base.texturePosition + base.textureSize + base.variantStride * (glfwGetTime() % 15).toInt()
val (u0, v0) = texture.pixelToUV(pos1)
val (u1, v1) = texture.pixelToUV(pos2)
builder.quadZ(-1f, -1f, 1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
}
run {
val pos1 = base.texturePosition + base.variantStride!! * ((glfwGetTime() + 1) % 15).toInt()
val pos2 = base.texturePosition + base.textureSize + base.variantStride * ((glfwGetTime() + 1) % 15).toInt()
val (u0, v0) = texture.pixelToUV(pos1)
val (u1, v1) = texture.pixelToUV(pos2)
builder.quadZ(-3f, -1f, -1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
}
run {
val pos1 = base.texturePosition + base.variantStride!! * ((glfwGetTime() + 2) % 15).toInt()
val pos2 = base.texturePosition + base.textureSize + base.variantStride * ((glfwGetTime() + 2) % 15).toInt()
val (u0, v0) = texture.pixelToUV(pos1)
val (u1, v1) = texture.pixelToUV(pos2)
builder.quadZ(3f, -1f, 1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
}
builder.upload(vbo, ebo, GL_STREAM_DRAW)
GLFlatAttributeListBuilder.VERTEX_TEXTURE.apply(vao, enable = true)
state.shaderVertexTexture.use()
state.activeTexture = 0
texture.bind()
state.shaderVertexTexture["_texture"] = 0
state.shaderVertexTexture["_transform"] = Matrix4f.IDENTITY.scale(0.5f, 0.5f)
texture.textureMagFilter = GL_NEAREST
texture.textureMinFilter = GL_NEAREST_MIPMAP_NEAREST
vbo.bind()
ebo.bind()
glDrawElements(GL_TRIANGLES, builder.indexCount, GL_UNSIGNED_INT, 0L)
checkForGLError()
vao.close()
vbo.close()
ebo.close()
}
companion object {
const val BASELINE_TEXTURE_SIZE = 8f
}
}