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.glfw.GLFW.glfwSetWindowShouldClose
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
@ -76,7 +75,17 @@ fun main() {
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 getMat2 = Starbound.tilesAccessID[materialID2.toInt()]
@ -86,7 +95,17 @@ fun main() {
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 tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
private val tileModifiers = HashMap<String, MaterialModifier>()
private val tileModifiersByID = Int2ObjectAVLTreeMap<MaterialModifier>()
private val projectiles = HashMap<String, ConfiguredProjectile>()
private val parallax = HashMap<String, ParallaxPrototype>()
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 tilesAccessID: Map<Int, TileDefinition> = Collections.unmodifiableMap(tilesByMaterialID)
val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles)
@ -176,6 +182,7 @@ object Starbound : IVFS {
loadStage(callback, this::loadTileMaterials, "materials")
loadStage(callback, this::loadProjectiles, "projectiles")
loadStage(callback, this::loadParallax, "parallax definitions")
loadStage(callback, this::loadMaterialModifiers, "material modifier definitions")
initializing = false
initialized = true
@ -320,8 +327,27 @@ object Starbound : IVFS {
}
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.vector.ndouble.Vector2d
import java.io.Closeable
import java.util.LinkedList
/**
* Псевдо 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 val layers = TileLayerList()
val bakedMeshes = ArrayList<Pair<BakedStaticMesh, Int>>()
val bakedMeshes = LinkedList<Pair<BakedStaticMesh, Int>>()
private var changeset = -1
fun bake(view: ITileChunk) {
@ -46,8 +47,13 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for ((pos, tile) in view.posToTile) {
if (tile != null) {
val renderer = state.tileRenderers.get(tile.def.materialName)
renderer.tesselate(view, layers, pos, background = isBackground)
state.tileRenderers.getTileRenderer(tile.def.materialName).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) {
for ((_, tile) in view.posToTile) {
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) {
for ((baked, builder, zLevel) in layers.buildList()) {
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
}
@ -93,7 +105,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildList()) {
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(BakedStaticMesh(baked, builder) to zLevel)
}
@ -205,22 +217,6 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
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] внешним кодом)
*/
open class BakedProgramState(
open class ConfiguredShaderProgram(
val program: GLShaderProgram,
) {
private val transformLocation = program["_transform"]
@ -40,13 +40,13 @@ open class BakedProgramState(
* с заданной матрицей трансформации
*/
class BakedStaticMesh(
val programState: BakedProgramState,
val programState: ConfiguredShaderProgram,
val indexCount: Int,
val vao: GLVertexArrayObject,
) : AutoCloseable {
private var onClose = {}
constructor(programState: BakedProgramState, builder: DynamicVertexBuilder) : this(
constructor(programState: ConfiguredShaderProgram, builder: DynamicVertexBuilder) : this(
programState,
builder.indexCount,
programState.program.state.newVAO(),

View File

@ -1,45 +1,44 @@
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.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.defs.tile.RenderMatch
import ru.dbotthepony.kstarbound.defs.tile.RenderPiece
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.ChunkTile
import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.nint.Vector2i
import kotlin.collections.HashMap
data class TileLayer(
val bakedProgramState: BakedProgramState,
val bakedProgramState: ConfiguredShaderProgram,
val vertexBuilder: DynamicVertexBuilder,
val zPos: Int)
val zPos: Int
)
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()}
for (layer in list) {
if (layer.zPos == zLevel) {
return layer.vertexBuilder
}
}
val computed = TileLayer(programState, compute.invoke(), zLevel)
list.add(computed)
return computed.vertexBuilder
/**
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
*
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
*/
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
}
fun buildList(): List<TileLayer> {
fun buildSortedLayerList(): List<TileLayer> {
val list = ArrayList<TileLayer>()
for (getList in layers.values) {
list.addAll(getList)
list.addAll(getList.values)
}
list.sortBy {
@ -56,19 +55,32 @@ class TileLayerList {
val isNotEmpty get() = layers.isNotEmpty()
}
/**
* Хранит в себе программы для отрисовки определённых [TileDefinition]
*
* Создаётся единожды как потомок [GLStateTracker]
*/
class TileRenderers(val state: GLStateTracker) {
private val foregroundTilePrograms = HashMap<GLTexture2D, ForegroundTileProgram>()
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 {
return tileRenderers.computeIfAbsent(tile) {
val def = Starbound.getTileDefinition(tile) // TODO: Пустой рендерер
fun getTileRenderer(defName: String): TileRenderer {
return tileRenderersCache.computeIfAbsent(defName) {
val def = Starbound.tilesAccess[defName] // TODO: Пустой рендерер
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() {
super.setup()
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() {
super.setup()
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)
}
/**
* Возвращает запечённое состояние shaderVertexTextureColor с данной текстурой
* Возвращает запечённое состояние шейдера shaderVertexTextureColor с данной текстурой
*/
fun background(texture: GLTexture2D): BakedProgramState {
fun background(texture: GLTexture2D): ConfiguredShaderProgram {
return backgroundTilePrograms.computeIfAbsent(texture, ::BackgroundTileProgram)
}
@ -151,11 +163,30 @@ private enum class TileRenderTesselateResult {
HALT
}
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
val texture = state.loadNamedTexture(tile.renderParameters.absoluteTexturePath).also {
private fun vertexTextureBuilder() = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
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
}
val equalityTester: EqualityRuleTester = when (def) {
is TileDefinition -> TileEqualityTester(def)
is MaterialModifier -> ModifierEqualityTester(def)
}
val bakedProgramState = state.tileRenderers.foreground(texture)
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
// private var notifiedDepth = false
@ -181,7 +212,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
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 (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
@ -192,7 +223,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
d,
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
} 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 (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 {
if (matchPiece.test(getter, tile, pos)) {
private fun tesselatePiece(
matchPiece: RenderMatch,
getter: ITileChunk,
layers: TileLayerList,
pos: Vector2i,
thisBuilder: DynamicVertexBuilder,
background: Boolean
): TileRenderTesselateResult {
if (matchPiece.test(getter, equalityTester, pos)) {
for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) {
val program: BakedProgramState
if (background) {
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
val program = if (background) {
state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
} 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) {
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}, pos, renderPiece.offset)
tesselateAt(renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset)
} else {
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) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
val template = tile.renderTemplate
val template = def.renderTemplate
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.renderParameters.zLevel) {
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}
val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder)
for ((_, matcher) in template.matches) {
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) {
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(
val modId: Int,
val modName: String,
val itemDrop: String,
val description: String,
val health: Int,
val harvestLevel: Int,
val breaksWithTile: Boolean,
val itemDrop: String? = null,
val description: String = "...",
val health: Int = 0,
val harvestLevel: Int = 0,
val breaksWithTile: Boolean = true,
val miningSounds: List<String> = listOf(),
val miningParticle: String? = null,
val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters
) {
override val renderTemplate: RenderTemplate,
override val renderParameters: RenderParameters
) : IRenderableTile {
init {
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 org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
import ru.dbotthepony.kstarbound.io.EnumAdapter
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ChunkTile
import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap
@ -37,6 +37,10 @@ data class RenderPiece(
}
}
fun interface EqualityRuleTester {
fun test(tile: ChunkTile?): Boolean
}
data class RenderRuleList(
val entries: List<Entry>,
val join: Combination = Combination.ALL
@ -50,9 +54,9 @@ data class RenderRuleList(
val matchHue: 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) {
"EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef
"EqualsSelf" -> equalityTester.test(getter[thisPos + offsetPos])
"Connects" -> getter[thisPos + offsetPos] != null
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) {
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 {
@ -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) {
Combination.ALL -> {
for (entry in entries) {
if (!entry.test(getter, thisRef, thisPos, offset)) {
if (!entry.test(getter, equalityTester, thisPos, offset)) {
return false
}
}
@ -101,7 +105,7 @@ data class RenderRuleList(
Combination.ANY -> {
for (entry in entries) {
if (entry.test(getter, thisRef, thisPos, offset)) {
if (entry.test(getter, equalityTester, thisPos, offset)) {
return true
}
}
@ -144,8 +148,8 @@ data class RenderMatch(
) {
var rule by WriteOnce<RenderRuleList>()
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
return rule.test(getter, thisRef, thisPos, offset)
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
return rule.test(getter, equalityTester, thisPos, offset)
}
fun resolve(template: RenderTemplate) {
@ -177,10 +181,12 @@ data class RenderMatch(
* Если хотя бы один из них вернул false, весь тест возвращает false
*
* [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) {
if (!matcher.test(getter, thisRef, thisPos)) {
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
return false
}
}
@ -190,7 +196,7 @@ data class RenderMatch(
}
for (matcher in matchAnyPoints) {
if (matcher.test(getter, thisRef, thisPos)) {
if (matcher.test(tileAccess, equalityTester, thisPos)) {
return true
}
}

View File

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

View File

@ -4,6 +4,7 @@ import ru.dbotthepony.kbox2d.api.BodyDef
import ru.dbotthepony.kbox2d.api.FixtureDef
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.entities.Entity
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
set(value) {
field = value
chunk.incChangeset()
if (value != field) {
field = value
chunk.incChangeset()
}
}
var forceVariant = -1
var variant = -1
set(value) {
field = value
chunk.incChangeset()
if (value != field) {
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 {