From 48cf2055066c8c9ad62f9b187f15a922db5e7351 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 10 Sep 2022 22:30:34 +0700 Subject: [PATCH] more shader stuff, hue shift, color variants, liquid def loader test --- .../kotlin/ru/dbotthepony/kstarbound/Ext.kt | 8 +++ .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 10 ++- .../ru/dbotthepony/kstarbound/Starbound.kt | 29 ++++++++ .../kstarbound/client/ClientChunk.kt | 4 +- .../kstarbound/client/gl/GLAttributeList.kt | 28 +++++--- .../kstarbound/client/gl/GLStateTracker.kt | 9 ++- .../kstarbound/client/gl/VertexBuilder.kt | 32 +++++++++ .../kstarbound/client/render/TileRenderer.kt | 66 +++++++++--------- .../defs/liquid/LiquidDefinition.kt | 53 +++++++++++++++ .../kstarbound/defs/tile/MaterialModifier.kt | 2 + .../kstarbound/defs/tile/RenderParameters.kt | 2 + .../kstarbound/defs/tile/RenderTemplate.kt | 4 +- .../kstarbound/io/KConcreteTypeAdapter.kt | 4 +- .../kstarbound/util/TwoDimensionalArray.kt | 35 ++++++++++ .../ru/dbotthepony/kstarbound/world/Chunk.kt | 68 +++++++++++++++---- .../dbotthepony/kstarbound/world/ChunkAPI.kt | 10 +-- .../dbotthepony/kstarbound/world/TileView.kt | 4 +- .../ru/dbotthepony/kstarbound/world/World.kt | 4 +- .../world/phys/RectTileFlooderDepthFirst.kt | 4 +- .../world/phys/RectTileFlooderSizeFirst.kt | 4 +- .../kstarbound/world/phys/TileFlooder.kt | 4 +- .../fragment/texture_color_per_vertex.glsl | 38 +++++++++++ .../resources/shaders/vertex/texture_hsv.glsl | 16 +++++ 23 files changed, 356 insertions(+), 82 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt create mode 100644 src/main/resources/shaders/fragment/texture_color_per_vertex.glsl create mode 100644 src/main/resources/shaders/vertex/texture_hsv.glsl diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt new file mode 100644 index 00000000..86d2162d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt @@ -0,0 +1,8 @@ +package ru.dbotthepony.kstarbound + +import com.google.gson.GsonBuilder +import com.google.gson.TypeAdapter + +inline fun GsonBuilder.registerTypeAdapter(adapter: TypeAdapter): GsonBuilder { + return registerTypeAdapter(T::class.java, adapter) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 0a749737..91deb112 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -12,6 +12,7 @@ import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File import java.util.zip.Inflater +import kotlin.math.roundToInt private val LOGGER = LogManager.getLogger() @@ -75,12 +76,17 @@ fun main() { hitTile = true } - reader.skipBytes(1) // Foreground hue shift - reader.skipBytes(1) // Foreground color variant + // reader.skipBytes(1) // Foreground hue shift + // reader.skipBytes(1) // Foreground color variant + val colorShift = reader.readUnsignedByte() + val colorVariant = reader.readUnsignedByte() val modifier = reader.readUnsignedShort() val getModifier = Starbound.tileModifiersByIDAccess[modifier] + chunk.foreground[x, y]?.color = colorVariant + chunk.foreground[x, y]?.setHueShift(colorShift) + if (getModifier != null && getMat != null) { chunk.foreground[x, y]?.modifier = getModifier } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index d5e1695a..9dc3fe94 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -8,6 +8,7 @@ import ru.dbotthepony.kstarbound.api.PhysicalFS import ru.dbotthepony.kstarbound.api.getPathFilename import ru.dbotthepony.kstarbound.api.getPathFolder import ru.dbotthepony.kstarbound.defs.* +import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.RenderParameters @@ -57,10 +58,15 @@ object Starbound : IVFS { private val tileModifiers = HashMap() private val tileModifiersByID = Int2ObjectAVLTreeMap() + private val liquid = HashMap() + private val liquidByID = Int2ObjectAVLTreeMap() + private val projectiles = HashMap() private val parallax = HashMap() private val functions = HashMap() + val liquidAccess: Map = Collections.unmodifiableMap(liquid) + val liquidByIDAccess: Map = Collections.unmodifiableMap(liquidByID) val tileModifiersAccess: Map = Collections.unmodifiableMap(tileModifiers) val tileModifiersByIDAccess: Map = Collections.unmodifiableMap(tileModifiersByID) val tilesAccess: Map = Collections.unmodifiableMap(tiles) @@ -94,6 +100,7 @@ object Starbound : IVFS { .also(RenderParameters::registerGson) .also(RenderTemplate::registerGson) .also(TileDefinition::registerGson) + .also(LiquidDefinition::registerGson) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) @@ -183,6 +190,7 @@ object Starbound : IVFS { loadStage(callback, this::loadProjectiles, "projectiles") loadStage(callback, this::loadParallax, "parallax definitions") loadStage(callback, this::loadMaterialModifiers, "material modifier definitions") + loadStage(callback, this::loadLiquidDefinitions, "liquid definitions") initializing = false initialized = true @@ -350,4 +358,25 @@ object Starbound : IVFS { readingFolder = null } + + private fun loadLiquidDefinitions(callback: (String) -> Unit) { + for (fs in fileSystems) { + for (listedFile in fs.listAllFilesWithExtension("liquid")) { + try { + callback("Loading $listedFile") + + readingFolder = getPathFolder(listedFile) + val liquidDef = gson.fromJson(getReader(listedFile), LiquidDefinition::class.java) + + check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" } + check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" } + } catch (err: Throwable) { + //throw TileDefLoadingException("Loading tile file $listedFile", err) + LOGGER.error("Loading liquid definition file $listedFile", err) + } + } + } + + readingFolder = null + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index b5792416..04ba733b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -47,12 +47,12 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk, VertexBuilderType> { fun expect(type: GLType): This fun pushVec3f(x: Float, y: Float, z: Float): This fun pushVec2f(x: Float, y: Float): This + fun push(value: Float): This fun end(): VertexBuilderType } @@ -132,6 +133,20 @@ typealias VertexTransformer = (IVertex<*, *>, Int) -> IVertex<*, *> private val emptyTransform: VertexTransformer = { it, _ -> it } private val EMPTY_BUFFER = ByteBuffer.allocateDirect(0) +fun VertexTransformer.before(other: VertexTransformer): VertexTransformer { + return { a, b -> + other.invoke(a, b) + this.invoke(a, b) + } +} + +fun VertexTransformer.after(other: VertexTransformer): VertexTransformer { + return { a, b -> + this.invoke(a, b) + other.invoke(a, b) + } +} + object VertexTransformers { fun uv(u0: Float, v0: Float, @@ -279,6 +294,7 @@ class DynamicVertexBuilder(val attributes: GLFlatAttributeList, override val typ is IntArray -> for (i in element) bytes.putInt(i) is ByteArray -> for (i in element) bytes.put(i) is DoubleArray -> for (i in element) bytes.putDouble(i) + is Float -> bytes.putFloat(element) else -> throw IllegalStateException("Unknown element $element") } } @@ -291,6 +307,7 @@ class DynamicVertexBuilder(val attributes: GLFlatAttributeList, override val typ is IntArray -> it.joinToString(", ") is ByteArray -> it.joinToString(", ") is DoubleArray -> it.joinToString(", ") + is Float -> it else -> "null" } }.joinToString("; ")})" } @@ -331,6 +348,12 @@ class DynamicVertexBuilder(val attributes: GLFlatAttributeList, override val typ return this } + override fun push(value: Float): Vertex { + expect(GLType.FLOAT) + store[index++] = value + return this + } + override fun checkValid() { for (elem in store.indices) { if (store[elem] == null) { @@ -521,6 +544,15 @@ class StreamVertexBuilder( return this } + override fun push(value: Float): Vertex { + expect(GLType.FLOAT) + vertexBuffer.position(bufferPosition) + vertexBuffer.putFloat(value) + index++ + bufferPosition += 4 + return this + } + override fun end(): StreamVertexBuilder { check(index == attributes.size) { "Vertex $vertexIndex is not fully filled (only $index attributes provided, ${attributes.size} required)" } return this@StreamVertexBuilder diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 039aeb9b..a1cacabf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -8,7 +8,7 @@ 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.* -import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kstarbound.world.TileState import ru.dbotthepony.kstarbound.world.ITileChunk import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.nint.Vector2i @@ -80,7 +80,7 @@ class TileRenderers(val state: GLStateTracker) { } } - private inner class ForegroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTexture) { + private inner class ForegroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTextureHSVColor) { override fun setup() { super.setup() state.activeTexture = 0 @@ -89,6 +89,8 @@ class TileRenderers(val state: GLStateTracker) { texture.bind() texture.textureMagFilter = GL_NEAREST texture.textureMinFilter = GL_NEAREST + + program["_color"] = FOREGROUND_COLOR } override fun equals(other: Any?): Boolean { @@ -108,7 +110,7 @@ class TileRenderers(val state: GLStateTracker) { } } - private inner class BackgroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTextureColor) { + private inner class BackgroundTileProgram(private val texture: GLTexture2D) : ConfiguredShaderProgram(state.shaderVertexTextureHSVColor) { override fun setup() { super.setup() state.activeTexture = 0 @@ -154,6 +156,7 @@ class TileRenderers(val state: GLStateTracker) { companion object { val BACKGROUND_COLOR = Color(0.4f, 0.4f, 0.4f) + val FOREGROUND_COLOR = Color(1f, 1f, 1f) } } @@ -163,16 +166,16 @@ private enum class TileRenderTesselateResult { HALT } -private fun vertexTextureBuilder() = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS) +private fun vertexTextureBuilder() = DynamicVertexBuilder(GLFlatAttributeList.VERTEX_HSV_TEXTURE, VertexType.QUADS) private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { - override fun test(tile: ChunkTile?): Boolean { + override fun test(tile: TileState?): Boolean { return tile?.def == definition } } private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester { - override fun test(tile: ChunkTile?): Boolean { + override fun test(tile: TileState?): Boolean { return tile?.modifier == definition } } @@ -191,7 +194,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { val bakedBackgroundProgramState = state.tileRenderers.background(texture) // private var notifiedDepth = false - private fun tesselateAt(piece: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) { + private fun tesselateAt(self: TileState, piece: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) { val fx = pos.x.toFloat() val fy = pos.y.toFloat() @@ -212,38 +215,35 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { d += offset.y / PIXELS_IN_STARBOUND_UNITf } - 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) + var mins = piece.texturePosition + var maxs = piece.texturePosition + piece.textureSize - builder.quadZ( - a, - b, - c, - d, - Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0)) - } else { + if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.texture == null) { 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) - - builder.quadZ( - a, - b, - c, - d, - Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0)) + mins += piece.variantStride * variant + maxs += piece.variantStride * variant } + + if (def.renderParameters.multiColored && piece.colorStride != null && self.color != 0) { + mins += piece.colorStride * self.color + maxs += piece.colorStride * self.color + } + + val (u0, v0) = texture.pixelToUV(mins) + val (u1, v1) = texture.pixelToUV(maxs) + + builder.quadZ(a, b, c, d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (!isModifier || self.modifier?.grass == true) self.hueShift else 0f) }) } private fun tesselatePiece( + self: TileState, matchPiece: RenderMatch, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, - background: Boolean + background: Boolean, + isModifier: Boolean, ): TileRenderTesselateResult { if (matchPiece.test(getter, equalityTester, pos)) { for (renderPiece in matchPiece.pieces) { @@ -254,14 +254,14 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) } - tesselateAt(renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset) + tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier) } else { - tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset) + tesselateAt(self, renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset, isModifier) } } for (subPiece in matchPiece.subMatches) { - val matched = tesselatePiece(subPiece, getter, layers, pos, thisBuilder, background) + val matched = tesselatePiece(self, subPiece, getter, layers, pos, thisBuilder, background, isModifier) if (matched == TileRenderTesselateResult.HALT || matched == TileRenderTesselateResult.CONTINUE && matchPiece.haltOnSubMatch) { return TileRenderTesselateResult.HALT @@ -287,7 +287,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { * * Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf] */ - fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) { + fun tesselate(self: TileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) { // если у нас нет renderTemplate // то мы просто не можем его отрисовать val template = def.renderTemplate @@ -296,7 +296,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { for ((_, matcher) in template.matches) { for (matchPiece in matcher) { - val matched = tesselatePiece(matchPiece, getter, layers, pos, vertexBuilder, background) + val matched = tesselatePiece(self, matchPiece, getter, layers, pos, vertexBuilder, background, isModifier) if (matched == TileRenderTesselateResult.HALT) { break diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt new file mode 100644 index 00000000..ccf0c0ea --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/liquid/LiquidDefinition.kt @@ -0,0 +1,53 @@ +package ru.dbotthepony.kstarbound.defs.liquid + +import com.google.gson.GsonBuilder +import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter +import ru.dbotthepony.kstarbound.registerTypeAdapter +import ru.dbotthepony.kvector.vector.Color + +data class LiquidDefinition( + val name: String, + val liquidId: Int, + val description: String = "...", + val tickDelta: Int = 1, + val color: Color, + val itemDrop: String? = null, + val statusEffects: List = listOf(), + val interactions: List = listOf(), + val texture: String, + val bottomLightMix: Color, + val textureMovementFactor: Double, +) { + data class Interaction(val liquid: Int, val liquidResult: Int? = null, val materialResult: String? = null) { + init { + require(liquidResult != null || materialResult != null) { "Both liquidResult and materialResult are missing" } + } + } + + companion object { + val ADAPTER = KConcreteTypeAdapter.Builder(LiquidDefinition::class) + .plain(LiquidDefinition::name) + .plain(LiquidDefinition::liquidId) + .plain(LiquidDefinition::description) + .plain(LiquidDefinition::tickDelta) + .plain(LiquidDefinition::color) + .plain(LiquidDefinition::itemDrop) + .list(LiquidDefinition::statusEffects) + .list(LiquidDefinition::interactions) + .plain(LiquidDefinition::texture) + .plain(LiquidDefinition::bottomLightMix) + .plain(LiquidDefinition::textureMovementFactor) + .build() + + val INTERACTION_ADAPTER = KConcreteTypeAdapter.Builder(Interaction::class) + .plain(Interaction::liquid) + .plain(Interaction::liquidResult) + .plain(Interaction::materialResult) + .build() + + fun registerGson(gsonBuilder: GsonBuilder) { + gsonBuilder.registerTypeAdapter(ADAPTER) + gsonBuilder.registerTypeAdapter(INTERACTION_ADAPTER) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt index 7eb0847f..49dc0e0d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt @@ -11,6 +11,7 @@ data class MaterialModifier( val health: Int = 0, val harvestLevel: Int = 0, val breaksWithTile: Boolean = true, + val grass: Boolean = false, val miningSounds: List = listOf(), val miningParticle: String? = null, override val renderTemplate: RenderTemplate, @@ -29,6 +30,7 @@ data class MaterialModifier( .plain(MaterialModifier::health) .plain(MaterialModifier::harvestLevel) .plain(MaterialModifier::breaksWithTile) + .plain(MaterialModifier::grass) .list(MaterialModifier::miningSounds) .plain(MaterialModifier::miningParticle) .plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt index 48b80930..23eab3a0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt @@ -4,6 +4,8 @@ import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter +const val TILE_COLOR_VARIANTS = 9 + data class RenderParameters( val texture: String, val variants: Int = 0, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt index ef47ea20..57b272e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -12,7 +12,7 @@ import ru.dbotthepony.kstarbound.Starbound 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.TileState import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kvector.vector.nint.Vector2i import java.util.concurrent.ConcurrentHashMap @@ -38,7 +38,7 @@ data class RenderPiece( } fun interface EqualityRuleTester { - fun test(tile: ChunkTile?): Boolean + fun test(tile: TileState?): Boolean } data class RenderRuleList( diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt index 41a0be89..815df8d0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt @@ -279,7 +279,7 @@ class KConcreteTypeAdapter( val name = fieldId.toString() if (loggedMisses.add(name)) { - LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field") + LOGGER.warn("Skipping JSON field with name $name because ${bound.simpleName} has no such field") } reader.skipValue() @@ -306,7 +306,7 @@ class KConcreteTypeAdapter( if (fieldId == -1) { if (loggedMisses.add(name)) { - LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field") + LOGGER.warn("Skipping JSON field with name $name because ${bound.simpleName} has no such field") } reader.skipValue() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt new file mode 100644 index 00000000..4e0dca75 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/TwoDimensionalArray.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.kstarbound.util + +import kotlin.reflect.KClass + +class TwoDimensionalArray(clazz: KClass, private val width: Int, private val height: Int) { + private val memory: Array = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array + + operator fun get(x: Int, y: Int): T? { + if (x !in 0 until width) { + throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") + } + + if (y !in 0 until height) { + throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") + } + + return memory[x + y * width] + } + + operator fun set(x: Int, y: Int, value: T): T? { + if (x !in 0 until width) { + throw IndexOutOfBoundsException("X $x is out of bounds between 0 and $width") + } + + if (y !in 0 until height) { + throw IndexOutOfBoundsException("Y $y is out of bounds between 0 and $height") + } + + val old = memory[x + y * width] + memory[x + y * width] = value + return old + } +} + +inline fun TwoDimensionalArray(width: Int, height: Int) = TwoDimensionalArray(T::class, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index 98988ff4..ea8dfbbf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -4,8 +4,11 @@ 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.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier +import ru.dbotthepony.kstarbound.defs.tile.TILE_COLOR_VARIANTS import ru.dbotthepony.kstarbound.defs.tile.TileDefinition +import ru.dbotthepony.kstarbound.util.TwoDimensionalArray import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst @@ -18,19 +21,46 @@ import kotlin.collections.HashSet /** * Представляет из себя класс, который содержит состояние тайла на заданной позиции */ -class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) { +class TileState(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) { var color = 0 set(value) { if (value != field) { + if (!def.renderParameters.multiColored) { + throw IllegalStateException("${def.materialName} can't be colored") + } + + if (value !in 0 until TILE_COLOR_VARIANTS) { + throw IndexOutOfBoundsException("Tile variant $value is out of possible range 0 to $TILE_COLOR_VARIANTS") + } + field = value chunk.incChangeset() } } - var variant = -1 + /** + * Выставляет hue shift как байтовое значение в диапазоне 0 .. 255 + */ + fun setHueShift(value: Int) { + if (value < 0) { + hueShift = 0f + } else if (value > 255) { + hueShift = 360f + } else { + hueShift = (value / 255f) * 360f + } + } + + var hueShift = 0f set(value) { - if (value != field) { - field = value + var newValue = value % 360f + + if (newValue < 0f) { + newValue += 360f + } + + if (newValue != field) { + field = newValue chunk.incChangeset() } } @@ -44,23 +74,28 @@ class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) { } override fun equals(other: Any?): Boolean { - return other is ChunkTile && other.color == color && other.variant == variant && other.modifier === modifier && other.def === def + return other is TileState && other.color == color && other.modifier === modifier && other.def === def } override fun toString(): String { - return "ChunkTile[$chunk, material = ${def.materialName}, color = $color, variant = $variant, modifier = ${modifier?.modName}]" + return "ChunkTile[$chunk, material = ${def.materialName}, color = $color, 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 } } +data class LiquidState(val chunk: Chunk<*, *>.TileLayer, val def: LiquidDefinition) { + var pressure: Float = 0f + var level: Float = 1f + var isInfinite: Boolean = false +} + private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double { if (point.x > 0.0) { return point.dot(axis) @@ -308,16 +343,16 @@ abstract class Chunk, This : Chunk(CHUNK_SIZE * CHUNK_SIZE) + private val tiles = arrayOfNulls(CHUNK_SIZE * CHUNK_SIZE) - override operator fun get(x: Int, y: Int): ChunkTile? { + override operator fun get(x: Int, y: Int): TileState? { if (isOutside(x, y)) return null return tiles[x or (y shl CHUNK_SHIFT)] } - operator fun set(x: Int, y: Int, tile: ChunkTile?) { + operator fun set(x: Int, y: Int, tile: TileState?) { if (isOutside(x, y)) throw IndexOutOfBoundsException("Trying to set tile ${tile?.def?.materialName} at $x $y, but that is outside of chunk's range") @@ -326,11 +361,11 @@ abstract class Chunk, This : Chunk, This : Chunk = TwoDimensionalArray(CHUNK_SIZE, CHUNK_SIZE) + protected val entities = HashSet() - val entitiesAccess = Collections.unmodifiableSet(entities) + val entitiesAccess: Set = Collections.unmodifiableSet(entities) protected abstract fun onEntityAdded(entity: Entity) protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This) @@ -396,8 +434,8 @@ abstract class Chunk, This : Chunk> get() { - return object : Iterator> { + val posToTile: Iterator> get() { + return object : Iterator> { private var x = 0 private var y = 0 @@ -49,7 +49,7 @@ interface ITileGetter : ITileMap { return idx() < CHUNK_SIZE * CHUNK_SIZE } - override fun next(): Pair { + override fun next(): Pair { if (!hasNext()) { throw IllegalStateException("Already iterated everything!") } @@ -121,7 +121,7 @@ interface ITileSetter : ITileMap { /** * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка */ - operator fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? + operator fun set(x: Int, y: Int, tile: TileDefinition?): TileState? /** * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка */ diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt index b83db973..681b891f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt @@ -22,7 +22,7 @@ open class TileView( open val bottomLeft: ITileChunk?, open val bottomRight: ITileChunk?, ) : ITileChunk { - override fun get(x: Int, y: Int): ChunkTile? { + override fun get(x: Int, y: Int): TileState? { if (x in 0 ..CHUNK_SIZE_FF) { if (y in 0 ..CHUNK_SIZE_FF) { return center[x, y] @@ -75,7 +75,7 @@ class MutableTileView( override val bottomLeft: IMutableTileChunk?, override val bottomRight: IMutableTileChunk?, ) : TileView(center, right, top, topRight, topLeft, left, bottom, bottomLeft, bottomRight), IMutableTileChunk { - override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? { + override fun set(x: Int, y: Int, tile: TileDefinition?): TileState? { if (x in 0 .. CHUNK_SIZE_FF) { if (y in 0 .. CHUNK_SIZE_FF) { return center.set(x, y, tile) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index e96ebe2e..5a0a118a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -323,7 +323,7 @@ abstract class World, ChunkType : Chunk, ChunkType : Chunk, + private val tiles: Array, private val seen: BooleanArray, rootx: Int, rooty: Int diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt index e32c97a4..c6f7a6f1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt @@ -2,11 +2,11 @@ package ru.dbotthepony.kstarbound.world.phys import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF -import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kstarbound.world.TileState import ru.dbotthepony.kvector.vector.nint.Vector2i class RectTileFlooderSizeFirst( - private val tiles: Array, + private val tiles: Array, private val seen: BooleanArray, private val rootx: Int, private val rooty: Int diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt index 68eb397b..415db2b0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt @@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.world.phys import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF -import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kstarbound.world.TileState import ru.dbotthepony.kvector.vector.ndouble.Vector2d private data class TileExposure( @@ -14,7 +14,7 @@ private data class TileExposure( ) private class TileFlooder( - private val tiles: Array, + private val tiles: Array, private val seen: BooleanArray, rootx: Int, rooty: Int diff --git a/src/main/resources/shaders/fragment/texture_color_per_vertex.glsl b/src/main/resources/shaders/fragment/texture_color_per_vertex.glsl new file mode 100644 index 00000000..ad64b66d --- /dev/null +++ b/src/main/resources/shaders/fragment/texture_color_per_vertex.glsl @@ -0,0 +1,38 @@ + +#version 460 + +uniform sampler2D _texture; +uniform vec4 _color; + +in vec2 _uv_out; +in float _hsv_vertex; +out vec4 _color_out; + +// https://gist.github.com/983/e170a24ae8eba2cd174f +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + vec4 texel = texture(_texture, _uv_out); + + vec3 hsv2 = rgb2hsv(vec3(texel[0], texel[1], texel[2])); + hsv2[0] += _hsv_vertex / 360; + vec4 rgb2 = vec4(hsv2rgb(hsv2), texel[3]); + + _color_out = _color * rgb2; +} diff --git a/src/main/resources/shaders/vertex/texture_hsv.glsl b/src/main/resources/shaders/vertex/texture_hsv.glsl new file mode 100644 index 00000000..86728b0b --- /dev/null +++ b/src/main/resources/shaders/vertex/texture_hsv.glsl @@ -0,0 +1,16 @@ + +#version 460 + +layout (location = 0) in vec3 _pos; +layout (location = 1) in vec2 _uv_in; +layout (location = 2) in float _hsv_in; + +out vec2 _uv_out; +out float _hsv_vertex; +uniform mat4 _transform; + +void main() { + _uv_out = _uv_in; + _hsv_vertex = _hsv_in; + gl_Position = _transform * vec4(_pos, 1.0); +}