From 9da968695ed24c8db18fac2123758342c933deea Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 21 Feb 2023 22:49:25 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=D0=B0=D1=82=D0=BE=D1=80=D0=B0,=20RebindableSprite=20?= =?UTF-8?q?=D0=B8=20=D0=B5=D1=89=D1=91=20=D0=BF=D1=80=D0=B8=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=20=D0=B2=20GL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 12 ++++ .../kstarbound/client/ClientChunk.kt | 2 +- .../kstarbound/client/gl/GLStateTracker.kt | 70 +++++-------------- .../kstarbound/client/gl/GLTexture.kt | 16 +---- .../kstarbound/client/render/Animator.kt | 31 +++++++- .../kstarbound/client/render/BoundSprite.kt | 25 +------ .../kstarbound/client/render/FrameAnimator.kt | 17 +++++ .../client/render/RebindableSprite.kt | 50 +++++++++++++ .../kstarbound/client/render/TileRenderer.kt | 6 +- .../client/render/entity/EntityRenderer.kt | 18 ++--- .../client/render/entity/ItemRenderer.kt | 16 ++--- .../defs/image/AtlasConfiguration.kt | 11 ++- .../kstarbound/defs/image/ImageReference.kt | 15 +++- .../dbotthepony/kstarbound/util/SBPattern.kt | 12 +++- 14 files changed, 183 insertions(+), 118 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RebindableSprite.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 956cbfeb..544e206b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -6,6 +6,8 @@ import org.apache.logging.log4j.LogManager import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.render.Animator +import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.DynamicItemDefinition import ru.dbotthepony.kstarbound.io.BTreeDB import ru.dbotthepony.kstarbound.world.ChunkPos @@ -189,6 +191,16 @@ fun main() { } // println(Starbound.statusEffects["firecharge"]) + + starbound.pathStack.push("/animations/dust4") + val def = starbound.gson.fromJson(starbound.locate("/animations/dust4/dust4.animation").reader(), AnimationDefinition::class.java) + starbound.pathStack.pop() + + val animator = Animator(client.world!!, def) + + client.onPostDrawWorld { + animator.render(client.gl.matrixStack) + } } //ent.position += Vector2d(y = 14.0, x = -10.0) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index fb46f871..c4b2aa55 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -424,7 +424,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() override fun onEntityAdded(entity: Entity) { - entityRenderers[entity] = EntityRenderer.getRender(state, entity, this) + entityRenderers[entity] = EntityRenderer.getRender(world.client, entity, this) } override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt index cd28007e..9cab5d68 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLStateTracker.kt @@ -18,15 +18,12 @@ import ru.dbotthepony.kstarbound.client.render.Box2DRenderer import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.matrix.Matrix4fStack -import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.Color import java.io.File -import java.io.FileNotFoundException import java.lang.ref.Cleaner import java.util.* import java.util.concurrent.ThreadFactory -import java.util.concurrent.atomic.AtomicLong import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.math.roundToInt @@ -437,61 +434,30 @@ class GLStateTracker(val locator: ISBFileLocator) { private val named2DTextures = HashMap() - fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D { - return named2DTextures.computeIfAbsent(path) { - if (!locator.exists(path)) { - throw FileNotFoundException("Unable to locate $path") - } - - return@computeIfAbsent newTexture(path).upload(locator.readDirect(path), memoryFormat, fileFormat).generateMips() - } - } - - fun loadNamedTexture(path: String): GLTexture2D { - return named2DTextures.computeIfAbsent(path) { - if (!locator.exists(path)) { - throw FileNotFoundException("Unable to locate $path") - } - - return@computeIfAbsent newTexture(path).upload(locator.readDirect(path)).generateMips() - } - } - - private var loadedEmptyTexture = false + private var missingTexture: GLTexture2D? = null private val missingTexturePath = "/assetmissing.png" - fun loadNamedTextureSafe(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D { - if (!loadedEmptyTexture) { - loadedEmptyTexture = true - named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), memoryFormat, fileFormat).generateMips() - } + fun loadTexture(path: String): GLTexture2D { + ensureSameThread() - return named2DTextures.computeIfAbsent(path) { - if (!locator.exists(path)) { - LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath) - return@computeIfAbsent named2DTextures[missingTexturePath]!! - } - - return@computeIfAbsent newTexture(path).upload(locator.readDirect(path), memoryFormat, fileFormat).generateMips() - } - } - - fun loadNamedTextureSafe(path: String): GLTexture2D { - if (!loadedEmptyTexture) { - loadedEmptyTexture = true - named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips() - } - - return named2DTextures.computeIfAbsent(path) { - if (!locator.exists(path)) { - LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath) - return@computeIfAbsent named2DTextures[missingTexturePath]!! - } - - return@computeIfAbsent newTexture(path).upload(locator.readDirect(path)).generateMips().also { + if (missingTexture == null) { + missingTexture = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also { + it.textureMinFilter = GL_NEAREST it.textureMagFilter = GL_NEAREST } } + + return named2DTextures.computeIfAbsent(path) { + if (!locator.exists(path)) { + LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath) + missingTexture!! + } else { + newTexture(path).upload(locator.readDirect(path)).generateMips().also { + it.textureMinFilter = GL_NEAREST + it.textureMagFilter = GL_NEAREST + } + } + } } fun bind(obj: VertexBufferObject): VertexBufferObject { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt index feeba689..4d61acb4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/GLTexture.kt @@ -13,7 +13,7 @@ class TextureLoadingException(message: String) : Throwable(message) data class UVCoord(val u: Float, val v: Float) -class GLTexturePropertyTracker(private val flag: Int, var value: Int) { +private class GLTexturePropertyTracker(private val flag: Int, private var value: Int) { operator fun getValue(thisRef: GLTexture2D, property: KProperty<*>): Int { return value } @@ -62,8 +62,6 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") : A return height.toFloat() / width.toFloat() } - private var mipsWarning = 2 - var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR) var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR) @@ -71,15 +69,6 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") : A var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT) fun bind(): GLTexture2D { - if (textureMinFilter != GL_LINEAR && textureMinFilter != GL_NEAREST) { - if (mipsWarning == 1) { - LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this) - mipsWarning = 0 - } else if (mipsWarning == 2) { - mipsWarning = 1 - } - } - state.texture2D = this return this } @@ -87,8 +76,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "") : A fun generateMips(): GLTexture2D { state.ensureSameThread() glGenerateTextureMipmap(pointer) - checkForGLError() - mipsWarning = 0 + checkForGLError("Generating texture mipmaps") return this } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Animator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Animator.kt index e4566f84..d13a14dd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Animator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Animator.kt @@ -1,24 +1,51 @@ package ru.dbotthepony.kstarbound.client.render +import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.ClientWorld import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kvector.matrix.Matrix4fStack +import ru.dbotthepony.kvector.vector.nfloat.Vector3f class Animator( val world: ClientWorld, - val def: AnimationDefinition + val def: AnimationDefinition, + val renderParams: ((String) -> String?)? = null ) { + inline val state get() = world.client.gl val frameAnimator: FrameAnimator? + val mainSprite: RebindableSprite? init { if (def.frames != null && def.animationCycle != null && def.frameNumber != null) { frameAnimator = FrameAnimator(lastFrame = def.frameNumber - 1, time = world.client.time, animationCycle = def.animationCycle) + mainSprite = RebindableSprite(world.client, def.frames, ::getRenderParam) } else { frameAnimator = null + mainSprite = null } } - fun render(stack: Matrix4fStack) { + private fun getRenderParam(name: String): String? { + if (name == "frame") { + return frameAnimator?.frameString + } + return renderParams?.invoke(name) + } + + fun render(stack: Matrix4fStack) { + frameAnimator?.advance() + val sprite = mainSprite?.update() ?: return + sprite.texture.bind() + + stack.push().translateWithMultiplication(Vector3f(world.client.camera.pos)) + + state.programs.textured.use() + state.programs.textured.transform = stack.last + state.activeTexture = 0 + state.programs.textured.texture = 0 + + state.flat2DTexturedQuads.singleSprite(sprite.width / PIXELS_IN_STARBOUND_UNITf, sprite.height / PIXELS_IN_STARBOUND_UNITf, 0.0, sprite.transformer) + stack.pop() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt index 37eb0a37..a846eb2a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt @@ -17,12 +17,12 @@ class BoundSprite( /** * Настоящая ширина спрайта, в пикселях */ - inline val width get() = sprite.width(texture.width) + val width = sprite.width(texture.width) /** * Настоящая высота спрайта, в пикселях */ - inline val height get() = sprite.height(texture.height) + val height = sprite.height(texture.height) override val u0: Float override val v0: Float @@ -41,24 +41,3 @@ class BoundSprite( fun bind() = texture.bind() val transformer = QuadTransformers.uv(u0, v0, u1, v1) } - -/** - * Создаёт связку текстуры-атласа + координгат спрайта на ней - */ -fun ImageReference.bind(texture: GLTexture2D): BoundSprite { - return BoundSprite(sprite!!, texture) -} - -/** - * Создаёт связку текстуры-атласа, которая загружается через [GLStateTracker.loadNamedTextureSafe] + координгат спрайта на ней - */ -fun ImageReference.bind(state: GLStateTracker): BoundSprite { - return BoundSprite(sprite!!, state.loadNamedTextureSafe(imagePath.value!!)) -} - -/** - * Создаёт связку текстуры-атласа + координгат спрайта на ней - */ -fun AtlasConfiguration.Sprite.bind(texture: GLTexture2D): BoundSprite { - return BoundSprite(this, texture) -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/FrameAnimator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/FrameAnimator.kt index d73c9e3e..6598d422 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/FrameAnimator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/FrameAnimator.kt @@ -32,6 +32,13 @@ class FrameAnimator( var frame = 0 private set + /** + * Эффективное преобразование [frame] в строку + */ + val frameString: String get() { + return framenames.getOrNull(frame) ?: frame.toString() + } + private var counter = 0.0 private var lastRender = time.seconds @@ -73,4 +80,14 @@ class FrameAnimator( lastRender = time.seconds } } + + companion object { + private val framenames = ArrayList() + + init { + for (i in 0 .. 500) { + framenames.add(i.toString()) + } + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RebindableSprite.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RebindableSprite.kt new file mode 100644 index 00000000..41b63aac --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RebindableSprite.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.kstarbound.client.render + +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.defs.image.ImageReference + +class RebindableSprite( + val client: StarboundClient, + ref: ImageReference, + val renderParams: ((String) -> String?)? = null +) { + var sprite: BoundSprite? = null + private set + + var ref: ImageReference = ref + private set + + init { + val unbound = ref.sprite + + if (unbound != null) { + sprite = BoundSprite(unbound, client.gl.loadTexture(ref.imagePath.value!!)) + } + } + + /** + * Обновляет [ref] и [sprite] по значениям, которые выдаст [renderParams] + * + * Возвращает новое значение [sprite] + */ + fun update(): BoundSprite? { + client.gl.ensureSameThread() + + if (renderParams == null) + return sprite + + val newRef = ref.with(renderParams) + + if (newRef !== ref) { + ref = newRef + sprite = null + val unbound = newRef.sprite + + if (unbound != null) { + sprite = BoundSprite(unbound, client.gl.loadTexture(newRef.imagePath.value!!)) + } + } + + return sprite + } +} 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 34045438..984fade4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -186,7 +186,7 @@ private class ModifierEqualityTester(val definition: MaterialModifier) : Equalit class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { val state get() = renderers.state - val texture = state.loadNamedTexture(def.renderParameters.texture.imagePath.value!!).also { + val texture = state.loadTexture(def.renderParameters.texture.imagePath.value!!).also { it.textureMagFilter = GL_NEAREST } @@ -255,9 +255,9 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { for (renderPiece in matchPiece.pieces) { if (renderPiece.piece.texture != null) { val program = if (background) { - renderers.background(state.loadNamedTexture(renderPiece.piece.texture!!)) + renderers.background(state.loadTexture(renderPiece.piece.texture!!)) } else { - renderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) + renderers.foreground(state.loadTexture(renderPiece.piece.texture!!)) } tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt index 313a2c92..843ed8b9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.render.entity import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import ru.dbotthepony.kstarbound.client.ClientChunk +import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.matrix.Matrix4fStack @@ -13,7 +14,8 @@ import java.io.Closeable * * Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход) */ -open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open var chunk: ClientChunk?) : Closeable { +open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) : Closeable { + inline val state: GLStateTracker get() = client.gl open val renderPos: Vector2d get() = entity.position open fun render(stack: Matrix4fStack) { @@ -39,20 +41,20 @@ open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open va */ const val Z_LEVEL_ENTITIES = 30000 - private val renderers = Reference2ObjectOpenHashMap, (state: GLStateTracker, entity: Entity, chunk: ClientChunk?) -> EntityRenderer>() + private val renderers = Reference2ObjectOpenHashMap, (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer>() @Suppress("unchecked_cast") - fun registerRenderer(clazz: Class, renderer: (state: GLStateTracker, entity: T, chunk: ClientChunk?) -> EntityRenderer) { - check(renderers.put(clazz, renderer as (state: GLStateTracker, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" } + fun registerRenderer(clazz: Class, renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) { + check(renderers.put(clazz, renderer as (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" } } - inline fun registerRenderer(noinline renderer: (state: GLStateTracker, entity: T, chunk: ClientChunk?) -> EntityRenderer) { + inline fun registerRenderer(noinline renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) { registerRenderer(T::class.java, renderer) } - fun getRender(state: GLStateTracker, entity: Entity, chunk: ClientChunk? = null): EntityRenderer { - val factory = renderers[entity::class.java] ?: return EntityRenderer(state, entity, chunk) - return factory.invoke(state, entity, chunk) + fun getRender(client: StarboundClient, entity: Entity, chunk: ClientChunk? = null): EntityRenderer { + val factory = renderers[entity::class.java] ?: return EntityRenderer(client, entity, chunk) + return factory.invoke(client, entity, chunk) } init { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt index fd844ca4..bcff3f91 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ItemRenderer.kt @@ -2,14 +2,14 @@ package ru.dbotthepony.kstarbound.client.render.entity import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.ClientChunk -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker -import ru.dbotthepony.kstarbound.client.render.bind +import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kstarbound.client.render.RebindableSprite import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kvector.matrix.Matrix4fStack -class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) { +class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(client, entity, chunk) { private val def = entity.def - private val textures = def.inventoryIcon?.stream()?.map { it.image.bind(state) }?.toList() ?: listOf() + private val textures = def.inventoryIcon?.stream()?.map { RebindableSprite(client, it.image) }?.toList() ?: listOf() override fun render(stack: Matrix4fStack) { if (textures.isEmpty()) @@ -20,10 +20,10 @@ class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk state.activeTexture = 0 state.programs.textured.texture = 0 - for (texture in textures) { - texture.bind() - - state.flat2DTexturedQuads.singleSprite(texture.width / PIXELS_IN_STARBOUND_UNITf, texture.height / PIXELS_IN_STARBOUND_UNITf, entity.movement.angle, texture.transformer) + for (unbound in textures) { + val sprite = unbound.update() ?: continue + sprite.texture.bind() + state.flat2DTexturedQuads.singleSprite(sprite.width / PIXELS_IN_STARBOUND_UNITf, sprite.height / PIXELS_IN_STARBOUND_UNITf, entity.movement.angle, sprite.transformer) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt index 2bc161ab..bbe43be5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt @@ -58,16 +58,25 @@ class AtlasConfiguration private constructor( } } + /** + * @return [Sprite] если он существует с данным [name], или `null` + */ operator fun get(name: String): Sprite? { return sprites[name] } + /** + * @return [Sprite] если он существует с данным [name], или `null` + */ operator fun get(name: Int): Sprite? { return intIndexed[name] } + /** + * @return [Sprite] если он существует с данным [name], или первый спрайт в атласе + */ fun any(name: String): Sprite { - return get(name) ?: first + return get(name) ?: any() } private val any by lazy { get("root") ?: get("0") ?: get("default") ?: first } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt index 193e1851..110ea88a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt @@ -13,7 +13,7 @@ import ru.dbotthepony.kstarbound.util.PathStack import ru.dbotthepony.kstarbound.util.SBPattern /** - * @see [AtlasConfiguration.Companion.get] + * @see [AtlasConfiguration.Registry.get] */ class ImageReference private constructor( val raw: DirectAssetReference, @@ -26,8 +26,9 @@ class ImageReference private constructor( * Спрайт, на которое ссылается данный референс, или `null` если: * * [atlas] равен `null` * * [spritePath] является шаблоном и определены не все значения + * * [spritePath] не является правильным именем спрайта внутри [atlas] (смотрим [AtlasConfiguration.get]) */ - val sprite by lazy { + val sprite by lazy(LazyThreadSafetyMode.NONE) { if (atlas == null) null else if (spritePath == null) @@ -38,7 +39,15 @@ class ImageReference private constructor( fun with(values: (String) -> String?): ImageReference { val imagePath = this.imagePath.with(values) - val spritePath = this.spritePath?.with(values) + var spritePath = this.spritePath?.with(values) + + if (spritePath == null) { + val frame = values.invoke("frame") + + if (frame != null) { + spritePath = SBPattern.FRAME.with(values) + } + } if (imagePath != this.imagePath || spritePath != this.spritePath) { if (imagePath != this.imagePath) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt index 1bee7005..59ce1329 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt @@ -52,8 +52,11 @@ class SBPattern private constructor( } fun resolve(values: (String) -> String?): String? { - if (names.isEmpty()) + if (names.isEmpty()) { return raw + } else if (pieces.size == 1) { + return pieces[0].resolve(values, params::get) + } val buffer = ArrayList(pieces.size) @@ -116,6 +119,9 @@ class SBPattern private constructor( @JvmField val EMPTY = raw("") + @JvmField + val FRAME = of("") + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == SBPattern::class.java) { return object : TypeAdapter() { @@ -126,7 +132,7 @@ class SBPattern private constructor( } override fun read(`in`: JsonReader): SBPattern? { - return interner.intern(of(strings.read(`in`) ?: return null)) + return of(strings.read(`in`) ?: return null) } } as TypeAdapter } @@ -159,7 +165,7 @@ class SBPattern private constructor( throw IllegalArgumentException("Malformed pattern string: $raw") } - pieces.add(Piece(name = raw.substring(open + 1, closing - 1))) + pieces.add(Piece(name = raw.substring(open + 1, closing))) i = closing + 1 } }