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() private val tileRenderers = HashMap() 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, 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, 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 } }