Material modifier render test

This commit is contained in:
DBotThePony 2022-09-08 21:24:54 +07:00
parent bd5a6f3259
commit 122a951b56
Signed by: DBot
GPG Key ID: DCC23B5715498507
10 changed files with 224 additions and 109 deletions

View File

@ -4,7 +4,6 @@ import org.apache.logging.log4j.LogManager
import org.lwjgl.Version import org.lwjgl.Version
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
@ -76,7 +75,17 @@ fun main() {
hitTile = true hitTile = true
} }
reader.skipBytes(5) reader.skipBytes(1) // Foreground hue shift
reader.skipBytes(1) // Foreground color variant
val modifier = reader.readShort()
val getModifier = Starbound.tileModifiersByIDAccess[modifier.toInt()]
if (getModifier != null && getMat != null) {
chunk.foreground[x, y]?.modifier = getModifier
}
reader.skipBytes(1) // Foreground mod hue shift
val materialID2 = reader.readShort() val materialID2 = reader.readShort()
val getMat2 = Starbound.tilesAccessID[materialID2.toInt()] val getMat2 = Starbound.tilesAccessID[materialID2.toInt()]
@ -86,7 +95,17 @@ fun main() {
hitTile = true hitTile = true
} }
reader.skipBytes(22) reader.skipBytes(1) // Background hue shift
reader.skipBytes(1) // Background color variant
val modifier2 = reader.readShort()
val getModifier2 = Starbound.tileModifiersByIDAccess[modifier2.toInt()]
if (getModifier2 != null && getMat2 != null) {
chunk.background[x, y]?.modifier = getModifier2
}
reader.skipBytes(18)
} }
} }

View File

@ -53,10 +53,16 @@ object Starbound : IVFS {
private val tiles = HashMap<String, TileDefinition>() private val tiles = HashMap<String, TileDefinition>()
private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>() private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
private val tileModifiers = HashMap<String, MaterialModifier>()
private val tileModifiersByID = Int2ObjectAVLTreeMap<MaterialModifier>()
private val projectiles = HashMap<String, ConfiguredProjectile>() private val projectiles = HashMap<String, ConfiguredProjectile>()
private val parallax = HashMap<String, ParallaxPrototype>() private val parallax = HashMap<String, ParallaxPrototype>()
private val functions = HashMap<String, JsonFunction>() private val functions = HashMap<String, JsonFunction>()
val tileModifiersAccess: Map<String, MaterialModifier> = Collections.unmodifiableMap(tileModifiers)
val tileModifiersByIDAccess: Map<Int, MaterialModifier> = Collections.unmodifiableMap(tileModifiersByID)
val tilesAccess: Map<String, TileDefinition> = Collections.unmodifiableMap(tiles) val tilesAccess: Map<String, TileDefinition> = Collections.unmodifiableMap(tiles)
val tilesAccessID: Map<Int, TileDefinition> = Collections.unmodifiableMap(tilesByMaterialID) val tilesAccessID: Map<Int, TileDefinition> = Collections.unmodifiableMap(tilesByMaterialID)
val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles) val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles)
@ -176,6 +182,7 @@ object Starbound : IVFS {
loadStage(callback, this::loadTileMaterials, "materials") loadStage(callback, this::loadTileMaterials, "materials")
loadStage(callback, this::loadProjectiles, "projectiles") loadStage(callback, this::loadProjectiles, "projectiles")
loadStage(callback, this::loadParallax, "parallax definitions") loadStage(callback, this::loadParallax, "parallax definitions")
loadStage(callback, this::loadMaterialModifiers, "material modifier definitions")
initializing = false initializing = false
initialized = true initialized = true
@ -320,8 +327,27 @@ object Starbound : IVFS {
} }
private fun loadMaterialModifiers(callback: (String) -> Unit) { private fun loadMaterialModifiers(callback: (String) -> Unit) {
for (fs in fileSystems) { readingFolder = "/tiles/materials"
for (fs in fileSystems) {
for (listedFile in fs.listAllFilesWithExtension("matmod")) {
try {
callback("Loading $listedFile")
readingFolder = getPathFolder(listedFile)
val tileDef = gson.fromJson(getReader(listedFile), MaterialModifier::class.java)
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
tileModifiersByID[tileDef.modId] = tileDef
tileModifiers[tileDef.modName] = tileDef
} catch (err: Throwable) {
//throw TileDefLoadingException("Loading tile file $listedFile", err)
LOGGER.error("Loading tile modifier file $listedFile", err)
}
}
} }
readingFolder = null
} }
} }

View File

@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.matrix.Matrix4fStack
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.io.Closeable import java.io.Closeable
import java.util.LinkedList
/** /**
* Псевдо zPos у фоновых тайлов * Псевдо zPos у фоновых тайлов
@ -24,7 +25,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable { private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
private val layers = TileLayerList() private val layers = TileLayerList()
val bakedMeshes = ArrayList<Pair<BakedStaticMesh, Int>>() val bakedMeshes = LinkedList<Pair<BakedStaticMesh, Int>>()
private var changeset = -1 private var changeset = -1
fun bake(view: ITileChunk) { fun bake(view: ITileChunk) {
@ -46,8 +47,13 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for ((pos, tile) in view.posToTile) { for ((pos, tile) in view.posToTile) {
if (tile != null) { if (tile != null) {
val renderer = state.tileRenderers.get(tile.def.materialName) state.tileRenderers.getTileRenderer(tile.def.materialName).tesselate(view, layers, pos, background = isBackground)
renderer.tesselate(view, layers, pos, background = isBackground)
val modifier = tile.modifier
if (modifier != null) {
state.tileRenderers.getModifierRenderer(modifier.modName).tesselate(view, layers, pos, background = isBackground)
}
} }
} }
} }
@ -55,13 +61,19 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
fun loadRenderers(view: ITileChunk) { fun loadRenderers(view: ITileChunk) {
for ((_, tile) in view.posToTile) { for ((_, tile) in view.posToTile) {
if (tile != null) { if (tile != null) {
state.tileRenderers.get(tile.def.materialName) state.tileRenderers.getTileRenderer(tile.def.materialName)
val modifier = tile.modifier
if (modifier != null) {
state.tileRenderers.getModifierRenderer(modifier.modName)
}
} }
} }
} }
fun uploadStatic(clear: Boolean = true) { fun uploadStatic(clear: Boolean = true) {
for ((baked, builder, zLevel) in layers.buildList()) { for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel) bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
} }
@ -93,7 +105,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
bakedMeshes.clear() bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildList()) { for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel) bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
} }
@ -205,22 +217,6 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
foregroundRenderer.autoUpload() foregroundRenderer.autoUpload()
} }
fun renderDebug() {
if (debugCollisions) {
state.quadWireframe {
it.quad(aabb.mins.x.toFloat(), aabb.mins.y.toFloat(), aabb.maxs.x.toFloat(), aabb.maxs.y.toFloat())
for (layer in foreground.collisionLayers()) {
it.quad(layer.mins.x.toFloat(), layer.mins.y.toFloat(), layer.maxs.x.toFloat(), layer.maxs.y.toFloat())
}
}
}
for (renderer in entityRenderers.values) {
renderer.renderDebug()
}
}
/** /**
* Хранит состояние отрисовки этого чанка * Хранит состояние отрисовки этого чанка
* *

View File

@ -18,7 +18,7 @@ import ru.dbotthepony.kvector.matrix.Matrix4fStack
* Ожидается, что состояние будет выставлено ПОЛНОСТЬЮ, т.е. НИКАКОЙ предыдущий код НЕ МОЖЕТ повлиять на результат выполнения * Ожидается, что состояние будет выставлено ПОЛНОСТЬЮ, т.е. НИКАКОЙ предыдущий код НЕ МОЖЕТ повлиять на результат выполнения
* шейдерной программы, которая связанна с этим объектом (за исключением не вызова [setTransform] внешним кодом) * шейдерной программы, которая связанна с этим объектом (за исключением не вызова [setTransform] внешним кодом)
*/ */
open class BakedProgramState( open class ConfiguredShaderProgram(
val program: GLShaderProgram, val program: GLShaderProgram,
) { ) {
private val transformLocation = program["_transform"] private val transformLocation = program["_transform"]
@ -40,13 +40,13 @@ open class BakedProgramState(
* с заданной матрицей трансформации * с заданной матрицей трансформации
*/ */
class BakedStaticMesh( class BakedStaticMesh(
val programState: BakedProgramState, val programState: ConfiguredShaderProgram,
val indexCount: Int, val indexCount: Int,
val vao: GLVertexArrayObject, val vao: GLVertexArrayObject,
) : AutoCloseable { ) : AutoCloseable {
private var onClose = {} private var onClose = {}
constructor(programState: BakedProgramState, builder: DynamicVertexBuilder) : this( constructor(programState: ConfiguredShaderProgram, builder: DynamicVertexBuilder) : this(
programState, programState,
builder.indexCount, builder.indexCount,
programState.program.state.newVAO(), programState.program.state.newVAO(),

View File

@ -1,45 +1,44 @@
package ru.dbotthepony.kstarbound.client.render package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.* import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.defs.tile.RenderMatch import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.defs.tile.RenderPiece import ru.dbotthepony.kstarbound.world.ChunkTile
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.ITileChunk import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
import kotlin.collections.HashMap import kotlin.collections.HashMap
data class TileLayer( data class TileLayer(
val bakedProgramState: BakedProgramState, val bakedProgramState: ConfiguredShaderProgram,
val vertexBuilder: DynamicVertexBuilder, val vertexBuilder: DynamicVertexBuilder,
val zPos: Int) val zPos: Int
)
class TileLayerList { class TileLayerList {
private val layers = HashMap<BakedProgramState, ArrayList<TileLayer>>() private val layers = HashMap<ConfiguredShaderProgram, Int2ObjectAVLTreeMap<TileLayer>>()
fun getLayer(programState: BakedProgramState, zLevel: Int, compute: () -> DynamicVertexBuilder): DynamicVertexBuilder { /**
val list = layers.computeIfAbsent(programState) {ArrayList()} * Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
*
for (layer in list) { * Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
if (layer.zPos == zLevel) { */
return layer.vertexBuilder fun computeIfAbsent(program: ConfiguredShaderProgram, zLevel: Int, compute: () -> DynamicVertexBuilder): DynamicVertexBuilder {
} return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
} return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
}).vertexBuilder
val computed = TileLayer(programState, compute.invoke(), zLevel)
list.add(computed)
return computed.vertexBuilder
} }
fun buildList(): List<TileLayer> { fun buildSortedLayerList(): List<TileLayer> {
val list = ArrayList<TileLayer>() val list = ArrayList<TileLayer>()
for (getList in layers.values) { for (getList in layers.values) {
list.addAll(getList) list.addAll(getList.values)
} }
list.sortBy { list.sortBy {
@ -56,19 +55,32 @@ class TileLayerList {
val isNotEmpty get() = layers.isNotEmpty() val isNotEmpty get() = layers.isNotEmpty()
} }
/**
* Хранит в себе программы для отрисовки определённых [TileDefinition]
*
* Создаётся единожды как потомок [GLStateTracker]
*/
class TileRenderers(val state: GLStateTracker) { class TileRenderers(val state: GLStateTracker) {
private val foregroundTilePrograms = HashMap<GLTexture2D, ForegroundTileProgram>() private val foregroundTilePrograms = HashMap<GLTexture2D, ForegroundTileProgram>()
private val backgroundTilePrograms = HashMap<GLTexture2D, BackgroundTileProgram>() private val backgroundTilePrograms = HashMap<GLTexture2D, BackgroundTileProgram>()
private val tileRenderers = HashMap<String, TileRenderer>() private val tileRenderersCache = HashMap<String, TileRenderer>()
private val modifierRenderersCache = HashMap<String, TileRenderer>()
operator fun get(tile: String): TileRenderer { fun getTileRenderer(defName: String): TileRenderer {
return tileRenderers.computeIfAbsent(tile) { return tileRenderersCache.computeIfAbsent(defName) {
val def = Starbound.getTileDefinition(tile) // TODO: Пустой рендерер val def = Starbound.tilesAccess[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(state, def!!) return@computeIfAbsent TileRenderer(state, def!!)
} }
} }
private inner class ForegroundTileProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTexture) { fun getModifierRenderer(defName: String): TileRenderer {
return modifierRenderersCache.computeIfAbsent(defName) {
val def = Starbound.tileModifiersAccess[defName] // TODO: Пустой рендерер
return@computeIfAbsent TileRenderer(state, def!!)
}
}
private inner class ForegroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTexture) {
override fun setup() { override fun setup() {
super.setup() super.setup()
state.activeTexture = 0 state.activeTexture = 0
@ -96,7 +108,7 @@ class TileRenderers(val state: GLStateTracker) {
} }
} }
private inner class BackgroundTileProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTextureColor) { private inner class BackgroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTextureColor) {
override fun setup() { override fun setup() {
super.setup() super.setup()
state.activeTexture = 0 state.activeTexture = 0
@ -127,16 +139,16 @@ class TileRenderers(val state: GLStateTracker) {
} }
/** /**
* Возвращает запечённое состояние shaderVertexTexture с данной текстурой * Возвращает запечённое состояние шейдера shaderVertexTexture с данной текстурой
*/ */
fun foreground(texture: GLTexture2D): BakedProgramState { fun foreground(texture: GLTexture2D): ConfiguredShaderProgram {
return foregroundTilePrograms.computeIfAbsent(texture, ::ForegroundTileProgram) return foregroundTilePrograms.computeIfAbsent(texture, ::ForegroundTileProgram)
} }
/** /**
* Возвращает запечённое состояние shaderVertexTextureColor с данной текстурой * Возвращает запечённое состояние шейдера shaderVertexTextureColor с данной текстурой
*/ */
fun background(texture: GLTexture2D): BakedProgramState { fun background(texture: GLTexture2D): ConfiguredShaderProgram {
return backgroundTilePrograms.computeIfAbsent(texture, ::BackgroundTileProgram) return backgroundTilePrograms.computeIfAbsent(texture, ::BackgroundTileProgram)
} }
@ -151,11 +163,30 @@ private enum class TileRenderTesselateResult {
HALT HALT
} }
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { private fun vertexTextureBuilder() = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
val texture = state.loadNamedTexture(tile.renderParameters.absoluteTexturePath).also {
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(tile: ChunkTile?): Boolean {
return tile?.def == definition
}
}
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
override fun test(tile: ChunkTile?): Boolean {
return tile?.modifier == definition
}
}
class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
val texture = state.loadNamedTexture(def.renderParameters.absoluteTexturePath).also {
it.textureMagFilter = GL_NEAREST it.textureMagFilter = GL_NEAREST
} }
val equalityTester: EqualityRuleTester = when (def) {
is TileDefinition -> TileEqualityTester(def)
is MaterialModifier -> ModifierEqualityTester(def)
}
val bakedProgramState = state.tileRenderers.foreground(texture) val bakedProgramState = state.tileRenderers.foreground(texture)
val bakedBackgroundProgramState = state.tileRenderers.background(texture) val bakedBackgroundProgramState = state.tileRenderers.background(texture)
// private var notifiedDepth = false // private var notifiedDepth = false
@ -181,7 +212,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
d += offset.y / PIXELS_IN_STARBOUND_UNITf d += offset.y / PIXELS_IN_STARBOUND_UNITf
} }
if (tile.renderParameters.variants == 0 || piece.texture != null || piece.variantStride == null) { if (def.renderParameters.variants == 0 || piece.texture != null || piece.variantStride == null) {
val (u0, v0) = texture.pixelToUV(piece.texturePosition) val (u0, v0) = texture.pixelToUV(piece.texturePosition)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
@ -192,7 +223,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
d, d,
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0)) Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
} else { } else {
val variant = (getter.randomDoubleFor(pos) * tile.renderParameters.variants).toInt() val variant = (getter.randomDoubleFor(pos) * def.renderParameters.variants).toInt()
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant) val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
@ -206,21 +237,24 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
} }
} }
private fun tesselatePiece(matchPiece: RenderMatch, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult { private fun tesselatePiece(
if (matchPiece.test(getter, tile, pos)) { matchPiece: RenderMatch,
getter: ITileChunk,
layers: TileLayerList,
pos: Vector2i,
thisBuilder: DynamicVertexBuilder,
background: Boolean
): TileRenderTesselateResult {
if (matchPiece.test(getter, equalityTester, pos)) {
for (renderPiece in matchPiece.pieces) { for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) { if (renderPiece.piece.texture != null) {
val program: BakedProgramState val program = if (background) {
state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
if (background) {
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
} else { } else {
program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!))
} }
tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.renderParameters.zLevel) { tesselateAt(renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset)
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}, pos, renderPiece.offset)
} else { } else {
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset) tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
} }
@ -256,15 +290,13 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) { fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
// если у нас нет renderTemplate // если у нас нет renderTemplate
// то мы просто не можем его отрисовать // то мы просто не можем его отрисовать
val template = tile.renderTemplate val template = def.renderTemplate
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.renderParameters.zLevel) { val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder)
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}
for ((_, matcher) in template.matches) { for ((_, matcher) in template.matches) {
for (matchPiece in matcher) { for (matchPiece in matcher) {
val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background) val matched = tesselatePiece(matchPiece, getter, layers, pos, vertexBuilder, background)
if (matched == TileRenderTesselateResult.HALT) { if (matched == TileRenderTesselateResult.HALT) {
break break

View File

@ -0,0 +1,6 @@
package ru.dbotthepony.kstarbound.defs.tile
sealed interface IRenderableTile {
val renderTemplate: RenderTemplate
val renderParameters: RenderParameters
}

View File

@ -6,16 +6,16 @@ import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
data class MaterialModifier( data class MaterialModifier(
val modId: Int, val modId: Int,
val modName: String, val modName: String,
val itemDrop: String, val itemDrop: String? = null,
val description: String, val description: String = "...",
val health: Int, val health: Int = 0,
val harvestLevel: Int, val harvestLevel: Int = 0,
val breaksWithTile: Boolean, val breaksWithTile: Boolean = true,
val miningSounds: List<String> = listOf(), val miningSounds: List<String> = listOf(),
val miningParticle: String? = null, val miningParticle: String? = null,
val renderTemplate: RenderTemplate, override val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters override val renderParameters: RenderParameters
) { ) : IRenderableTile {
init { init {
require(modId > 0) { "Invalid material modifier ID $modId" } require(modId > 0) { "Invalid material modifier ID $modId" }
} }

View File

@ -9,10 +9,10 @@ import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
import ru.dbotthepony.kstarbound.io.EnumAdapter import ru.dbotthepony.kstarbound.io.EnumAdapter
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ChunkTile
import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -37,6 +37,10 @@ data class RenderPiece(
} }
} }
fun interface EqualityRuleTester {
fun test(tile: ChunkTile?): Boolean
}
data class RenderRuleList( data class RenderRuleList(
val entries: List<Entry>, val entries: List<Entry>,
val join: Combination = Combination.ALL val join: Combination = Combination.ALL
@ -50,9 +54,9 @@ data class RenderRuleList(
val matchHue: Boolean = false, val matchHue: Boolean = false,
val inverse: Boolean = false, val inverse: Boolean = false,
) { ) {
private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { private fun doTest(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) { return when (type) {
"EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef "EqualsSelf" -> equalityTester.test(getter[thisPos + offsetPos])
"Connects" -> getter[thisPos + offsetPos] != null "Connects" -> getter[thisPos + offsetPos] != null
else -> { else -> {
@ -65,12 +69,12 @@ data class RenderRuleList(
} }
} }
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) { if (inverse) {
return !doTest(getter, thisRef, thisPos, offsetPos) return !doTest(getter, equalityTester, thisPos, offsetPos)
} }
return doTest(getter, thisRef, thisPos, offsetPos) return doTest(getter, equalityTester, thisPos, offsetPos)
} }
companion object { companion object {
@ -87,11 +91,11 @@ data class RenderRuleList(
} }
} }
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offset: Vector2i): Boolean { fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
when (join) { when (join) {
Combination.ALL -> { Combination.ALL -> {
for (entry in entries) { for (entry in entries) {
if (!entry.test(getter, thisRef, thisPos, offset)) { if (!entry.test(getter, equalityTester, thisPos, offset)) {
return false return false
} }
} }
@ -101,7 +105,7 @@ data class RenderRuleList(
Combination.ANY -> { Combination.ANY -> {
for (entry in entries) { for (entry in entries) {
if (entry.test(getter, thisRef, thisPos, offset)) { if (entry.test(getter, equalityTester, thisPos, offset)) {
return true return true
} }
} }
@ -144,8 +148,8 @@ data class RenderMatch(
) { ) {
var rule by WriteOnce<RenderRuleList>() var rule by WriteOnce<RenderRuleList>()
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
return rule.test(getter, thisRef, thisPos, offset) return rule.test(getter, equalityTester, thisPos, offset)
} }
fun resolve(template: RenderTemplate) { fun resolve(template: RenderTemplate) {
@ -177,10 +181,12 @@ data class RenderMatch(
* Если хотя бы один из них вернул false, весь тест возвращает false * Если хотя бы один из них вернул false, весь тест возвращает false
* *
* [subMatches] стоит итерировать только если это вернуло true * [subMatches] стоит итерировать только если это вернуло true
*
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
*/ */
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { fun test(tileAccess: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
for (matcher in matchAllPoints) { for (matcher in matchAllPoints) {
if (!matcher.test(getter, thisRef, thisPos)) { if (!matcher.test(tileAccess, equalityTester, thisPos)) {
return false return false
} }
} }
@ -190,7 +196,7 @@ data class RenderMatch(
} }
for (matcher in matchAnyPoints) { for (matcher in matchAnyPoints) {
if (matcher.test(getter, thisRef, thisPos)) { if (matcher.test(tileAccess, equalityTester, thisPos)) {
return true return true
} }
} }

View File

@ -19,9 +19,9 @@ data class TileDefinition(
val health: Double = 0.0, val health: Double = 0.0,
val category: String, val category: String,
val renderTemplate: RenderTemplate, override val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters, override val renderParameters: RenderParameters,
) { ) : IRenderableTile {
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(TileDefinition::class) val ADAPTER = KConcreteTypeAdapter.Builder(TileDefinition::class)
.plain( .plain(

View File

@ -4,6 +4,7 @@ import ru.dbotthepony.kbox2d.api.BodyDef
import ru.dbotthepony.kbox2d.api.FixtureDef import ru.dbotthepony.kbox2d.api.FixtureDef
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2Fixture import ru.dbotthepony.kbox2d.dynamics.B2Fixture
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst
@ -17,18 +18,47 @@ import kotlin.collections.HashSet
/** /**
* Представляет из себя класс, который содержит состояние тайла на заданной позиции * Представляет из себя класс, который содержит состояние тайла на заданной позиции
*/ */
data class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) { class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) {
var color = 0 var color = 0
set(value) { set(value) {
field = value if (value != field) {
chunk.incChangeset() field = value
chunk.incChangeset()
}
} }
var forceVariant = -1 var variant = -1
set(value) { set(value) {
field = value if (value != field) {
chunk.incChangeset() field = value
chunk.incChangeset()
}
} }
var modifier: MaterialModifier? = null
set(value) {
if (value != field) {
field = value
chunk.incChangeset()
}
}
override fun equals(other: Any?): Boolean {
return other is ChunkTile && other.color == color && other.variant == variant && other.modifier === modifier && other.def === def
}
override fun toString(): String {
return "ChunkTile[$chunk, material = ${def.materialName}, color = $color, variant = $variant, modifier = ${modifier?.modName}]"
}
override fun hashCode(): Int {
var result = chunk.hashCode()
result = 31 * result + def.hashCode()
result = 31 * result + color
result = 31 * result + variant
result = 31 * result + (modifier?.hashCode() ?: 0)
return result
}
} }
private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double { private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double {