more shader stuff, hue shift, color variants, liquid def loader test

This commit is contained in:
DBotThePony 2022-09-10 22:30:34 +07:00
parent 19cf788d87
commit 48cf205506
Signed by: DBot
GPG Key ID: DCC23B5715498507
23 changed files with 356 additions and 82 deletions

View File

@ -0,0 +1,8 @@
package ru.dbotthepony.kstarbound
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
return registerTypeAdapter(T::class.java, adapter)
}

View File

@ -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
}

View File

@ -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<String, MaterialModifier>()
private val tileModifiersByID = Int2ObjectAVLTreeMap<MaterialModifier>()
private val liquid = HashMap<String, LiquidDefinition>()
private val liquidByID = Int2ObjectAVLTreeMap<LiquidDefinition>()
private val projectiles = HashMap<String, ConfiguredProjectile>()
private val parallax = HashMap<String, ParallaxPrototype>()
private val functions = HashMap<String, JsonFunction>()
val liquidAccess: Map<String, LiquidDefinition> = Collections.unmodifiableMap(liquid)
val liquidByIDAccess: Map<Int, LiquidDefinition> = Collections.unmodifiableMap(liquidByID)
val tileModifiersAccess: Map<String, MaterialModifier> = Collections.unmodifiableMap(tileModifiers)
val tileModifiersByIDAccess: Map<Int, MaterialModifier> = Collections.unmodifiableMap(tileModifiersByID)
val tilesAccess: Map<String, TileDefinition> = 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
}
}

View File

@ -47,12 +47,12 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for ((pos, tile) in view.posToTile) {
if (tile != null) {
state.tileRenderers.getTileRenderer(tile.def.materialName).tesselate(view, layers, pos, background = isBackground)
state.tileRenderers.getTileRenderer(tile.def.materialName).tesselate(tile, view, layers, pos, background = isBackground)
val modifier = tile.modifier
if (modifier != null) {
state.tileRenderers.getModifierRenderer(modifier.modName).tesselate(view, layers, pos, background = isBackground)
state.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, pos, background = isBackground, isModifier = true)
}
}
}

View File

@ -23,6 +23,9 @@ enum class GLType(val identity: Int, val typeIndentity: Int, val byteSize: Int,
}
interface IGLAttributeList {
/**
* Применяет список атрибутов к заданному [GLVertexArrayObject] (попутно включая или отключая их через [enable])
*/
fun apply(target: GLVertexArrayObject, enable: Boolean = false)
}
@ -85,10 +88,15 @@ class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeLis
}
companion object {
val VEC2F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC2F)}.build()
val VEC3F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F)}.build()
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F).push(GLType.VEC2F)}.build()
val VERTEX_2D_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC2F).push(GLType.VEC2F)}.build()
val VEC2F = GLFlatAttributeListBuilder().push(GLType.VEC2F).build()
val VEC3F = GLFlatAttributeListBuilder().push(GLType.VEC3F).build()
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().push(GLType.VEC3F).push(GLType.VEC2F).build()
val VERTEX_HSV_TEXTURE = GLFlatAttributeListBuilder()
.push(GLType.VEC3F, GLType.VEC2F, GLType.FLOAT).build()
val VERTEX_2D_TEXTURE = GLFlatAttributeListBuilder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
@ -109,6 +117,11 @@ class GLFlatAttributeListBuilder : IGLAttributeList {
return push("$type#${attributes.size}", type)
}
fun push(vararg types: GLType): GLFlatAttributeListBuilder {
for (type in types) push(type)
return this
}
fun push(name: String, type: GLType): GLFlatAttributeListBuilder {
check(!findName(name)) { "Already has named attribute $name!" }
attributes.add(name to type)
@ -117,7 +130,7 @@ class GLFlatAttributeListBuilder : IGLAttributeList {
fun build() = GLFlatAttributeList(this)
@Deprecated("Используй build()")
@Deprecated("Используй build()", replaceWith = ReplaceWith("build()"))
override fun apply(target: GLVertexArrayObject, enable: Boolean) {
var offset = 0L
var stride = 0
@ -136,9 +149,4 @@ class GLFlatAttributeListBuilder : IGLAttributeList {
}
}
}
companion object {
val VEC3F = GLFlatAttributeList.VEC3F
val VERTEX_TEXTURE = GLFlatAttributeList.VERTEX_TEXTURE
}
}

View File

@ -227,7 +227,7 @@ class GLStateTracker {
checkForGLError()
}
val thread = Thread.currentThread()
val thread: Thread = Thread.currentThread()
val tileRenderers = TileRenderers(this)
fun ensureSameThread() {
@ -356,18 +356,25 @@ class GLStateTracker {
val shaderVertexTexture: GLTransformableProgram
val shaderVertexTextureColor: GLTransformableColorableProgram
val shaderVertexTextureHSVColor: GLTransformableColorableProgram
init {
val textureF = GLShader.internalFragment("shaders/fragment/texture.glsl")
val textureColorF = GLShader.internalFragment("shaders/fragment/texture_color.glsl")
val textureV = GLShader.internalVertex("shaders/vertex/texture.glsl")
val textureFragmentHSV = GLShader.internalFragment("shaders/fragment/texture_color_per_vertex.glsl")
val textureVertexHSV = GLShader.internalVertex("shaders/vertex/texture_hsv.glsl")
shaderVertexTexture = GLTransformableProgram(this, textureF, textureV)
shaderVertexTextureColor = GLTransformableColorableProgram(this, textureColorF, textureV)
shaderVertexTextureHSVColor = GLTransformableColorableProgram(this, textureFragmentHSV, textureVertexHSV)
textureF.unlink()
textureColorF.unlink()
textureV.unlink()
textureFragmentHSV.unlink()
textureVertexHSV.unlink()
}
val fontProgram: GLTransformableColorableProgram

View File

@ -125,6 +125,7 @@ interface IVertex<This : IVertex<This, VertexBuilderType>, 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

View File

@ -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

View File

@ -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<String> = listOf(),
val interactions: List<Interaction> = 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)
}
}
}

View File

@ -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<String> = 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)

View File

@ -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,

View File

@ -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(

View File

@ -279,7 +279,7 @@ class KConcreteTypeAdapter<T : Any>(
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<T : Any>(
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()

View File

@ -0,0 +1,35 @@
package ru.dbotthepony.kstarbound.util
import kotlin.reflect.KClass
class TwoDimensionalArray<T : Any>(clazz: KClass<T>, private val width: Int, private val height: Int) {
private val memory: Array<T?> = java.lang.reflect.Array.newInstance(clazz.java, width * height) as Array<T?>
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 <reified T : Any> TwoDimensionalArray(width: Int, height: Int) = TwoDimensionalArray(T::class, width, height)

View File

@ -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<WorldType : World<WorldType, This>, This : Chunk<WorldType,
/**
* Хранит тайлы как x + y * CHUNK_SIZE
*/
private val tiles = arrayOfNulls<ChunkTile>(CHUNK_SIZE * CHUNK_SIZE)
private val tiles = arrayOfNulls<TileState>(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<WorldType : World<WorldType, This>, This : Chunk<WorldType,
markPhysicsDirty()
}
override operator fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? {
override operator fun set(x: Int, y: Int, tile: TileDefinition?): TileState? {
if (isOutside(x, y))
throw IndexOutOfBoundsException("Trying to set tile ${tile?.materialName} at $x $y, but that is outside of chunk's range")
val chunkTile = if (tile != null) ChunkTile(this, tile) else null
val chunkTile = if (tile != null) TileState(this, tile) else null
this[x, y] = chunkTile
changeset++
markPhysicsDirty()
@ -344,8 +379,11 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val foreground = TileLayer()
val background = TileLayer()
protected val liquidStates: TwoDimensionalArray<LiquidState> = TwoDimensionalArray(CHUNK_SIZE, CHUNK_SIZE)
protected val entities = HashSet<Entity>()
val entitiesAccess = Collections.unmodifiableSet(entities)
val entitiesAccess: Set<Entity> = Collections.unmodifiableSet(entities)
protected abstract fun onEntityAdded(entity: Entity)
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
@ -396,8 +434,8 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val EMPTY = object : IMutableTileChunk {
override val pos = ChunkPos(0, 0)
override fun get(x: Int, y: Int): ChunkTile? = null
override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? = null
override fun get(x: Int, y: Int): TileState? = null
override fun set(x: Int, y: Int, tile: TileDefinition?): TileState? = null
}
private val aabbBase = AABB(

View File

@ -26,7 +26,7 @@ interface ITileGetter : ITileMap {
/**
* Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
*/
operator fun get(x: Int, y: Int): ChunkTile?
operator fun get(x: Int, y: Int): TileState?
/**
* Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
@ -38,8 +38,8 @@ interface ITileGetter : ITileMap {
*
* Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка
*/
val posToTile: Iterator<Pair<Vector2i, ChunkTile?>> get() {
return object : Iterator<Pair<Vector2i, ChunkTile?>> {
val posToTile: Iterator<Pair<Vector2i, TileState?>> get() {
return object : Iterator<Pair<Vector2i, TileState?>> {
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<Vector2i, ChunkTile?> {
override fun next(): Pair<Vector2i, TileState?> {
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?
/**
* Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
*/

View File

@ -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)

View File

@ -323,7 +323,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
)
}
fun getTile(pos: Vector2i): ChunkTile? {
fun getTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.foreground?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}
@ -333,7 +333,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
return chunk
}
fun getBackgroundTile(pos: Vector2i): ChunkTile? {
fun getBackgroundTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.background?.get(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}

View File

@ -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 RectTileFlooderDepthFirst(
private val tiles: Array<ChunkTile?>,
private val tiles: Array<TileState?>,
private val seen: BooleanArray,
rootx: Int,
rooty: Int

View File

@ -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<ChunkTile?>,
private val tiles: Array<TileState?>,
private val seen: BooleanArray,
private val rootx: Int,
private val rooty: Int

View File

@ -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<ChunkTile?>,
private val tiles: Array<TileState?>,
private val seen: BooleanArray,
rootx: Int,
rooty: Int

View File

@ -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;
}

View File

@ -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);
}