245 lines
8.1 KiB
Kotlin
245 lines
8.1 KiB
Kotlin
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
|
||
}
|
||
}
|