Material modifier render test
This commit is contained in:
parent
bd5a6f3259
commit
122a951b56
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Хранит состояние отрисовки этого чанка
|
* Хранит состояние отрисовки этого чанка
|
||||||
*
|
*
|
||||||
|
@ -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(),
|
@ -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
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.tile
|
||||||
|
|
||||||
|
sealed interface IRenderableTile {
|
||||||
|
val renderTemplate: RenderTemplate
|
||||||
|
val renderParameters: RenderParameters
|
||||||
|
}
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user