На самом деле, мы уже избавились от старого framegrid
This commit is contained in:
parent
69a5061e9e
commit
e263e29989
@ -1,355 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
|
||||||
import com.google.gson.*
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
|
||||||
import org.apache.logging.log4j.LogManager
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
class MalformedFrameGridException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause)
|
|
||||||
|
|
||||||
data class Frame(
|
|
||||||
val texture: String,
|
|
||||||
val texturePosition: Vector2i,
|
|
||||||
val textureSize: Vector2i,
|
|
||||||
) {
|
|
||||||
val textureEndPosition = texturePosition + textureSize
|
|
||||||
val width get() = textureSize.x
|
|
||||||
val height get() = textureSize.y
|
|
||||||
|
|
||||||
val aspectRatioWH: Float get() {
|
|
||||||
if (height == 0) {
|
|
||||||
return 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
return width.toFloat() / height.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
val aspectRatioHW: Float get() {
|
|
||||||
if (width == 0) {
|
|
||||||
return 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
return height.toFloat() / width.toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FrameSet(
|
|
||||||
val texture: String,
|
|
||||||
val name: String,
|
|
||||||
val frames: List<Frame>,
|
|
||||||
) {
|
|
||||||
val frameCount get() = frames.size
|
|
||||||
fun frame(num: Int) = frames[num]
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FrameSetBuilder(val name: String) {
|
|
||||||
val frames = Object2ObjectArrayMap<String, Frame>()
|
|
||||||
|
|
||||||
fun build(texture: String): FrameSet {
|
|
||||||
val list = ImmutableList.builder<Frame>()
|
|
||||||
val rebuild = Int2ObjectArrayMap<Frame>()
|
|
||||||
|
|
||||||
for ((k, v) in frames) {
|
|
||||||
val int = k.toIntOrNull()
|
|
||||||
|
|
||||||
if (int != null) {
|
|
||||||
rebuild[int] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rebuild.size == 0) {
|
|
||||||
throw IllegalStateException("Frame Set $name is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0 until rebuild.size) {
|
|
||||||
list.add(rebuild[i] ?: throw IllegalStateException("Frame Set $name has gap at $i"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return FrameSet(
|
|
||||||
texture = texture,
|
|
||||||
name = name,
|
|
||||||
frames = list.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IFrameGrid {
|
|
||||||
val texture: String
|
|
||||||
val size: Vector2i
|
|
||||||
val dimensions: Vector2i
|
|
||||||
val frames: List<FrameSet>
|
|
||||||
val frameCount get() = frames.size
|
|
||||||
val isResolved: Boolean
|
|
||||||
|
|
||||||
val textureSize get() = size * dimensions
|
|
||||||
|
|
||||||
fun resolve(determinedSize: Vector2i)
|
|
||||||
|
|
||||||
operator fun get(index: Int) = frames[index]
|
|
||||||
|
|
||||||
operator fun get(index: String): FrameSet {
|
|
||||||
for (frame in frames) {
|
|
||||||
if (index == frame.name) {
|
|
||||||
return frame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw IndexOutOfBoundsException("No such frame strip with name $index")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun splitName(textureName: String, input: String, warn: Boolean, lazy: () -> String): Pair<String, String> {
|
|
||||||
val split = input.split(':')
|
|
||||||
val frameName: String
|
|
||||||
val setName: String
|
|
||||||
|
|
||||||
when (split.size) {
|
|
||||||
1 -> {
|
|
||||||
setName = "root"
|
|
||||||
frameName = split[0]
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
setName = split[0]
|
|
||||||
frameName = split[1]
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("${lazy.invoke()}: Malformed frame name $input")
|
|
||||||
}
|
|
||||||
|
|
||||||
val frameNumber = frameName.toIntOrNull()
|
|
||||||
|
|
||||||
if (frameNumber == null) {
|
|
||||||
if (warn)
|
|
||||||
LOGGER.warn("{}: Frame {} will be discarded after frame grid is built, because it is not an integer", textureName, frameName)
|
|
||||||
} else {
|
|
||||||
require(frameNumber >= 0) { "${lazy.invoke()}: Frame number of $frameNumber does not make any sense" }
|
|
||||||
}
|
|
||||||
|
|
||||||
return setName to frameName
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateFakeNames(dimensions: Vector2i): JsonArray {
|
|
||||||
return JsonArray(dimensions.y).also {
|
|
||||||
var stripElem = 0
|
|
||||||
|
|
||||||
for (stripNum in 0 until dimensions.y) {
|
|
||||||
val strip = JsonArray(dimensions.x)
|
|
||||||
|
|
||||||
for (i in 0 until dimensions.x) {
|
|
||||||
strip.add(stripElem.toString())
|
|
||||||
stripElem++
|
|
||||||
}
|
|
||||||
|
|
||||||
it.add(strip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Так как объект довольно сложный для автоматической десериализации через gson
|
|
||||||
* поэтому вот так
|
|
||||||
*/
|
|
||||||
fun fromJson(input: JsonObject, texture: String): IFrameGrid {
|
|
||||||
val frameGrid: JsonObject
|
|
||||||
val aliases: JsonObject
|
|
||||||
val texturePath = "$texture.png".intern()
|
|
||||||
|
|
||||||
if (input["frameGrid"] is JsonObject) {
|
|
||||||
frameGrid = input["frameGrid"] as JsonObject
|
|
||||||
} else {
|
|
||||||
frameGrid = input
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input["aliases"] is JsonObject) {
|
|
||||||
aliases = input["aliases"] as JsonObject
|
|
||||||
} else {
|
|
||||||
aliases = JsonObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
val size = Starbound.gson.fromJson(frameGrid["size"], Vector2i::class.java) ?: throw IllegalArgumentException("Size is missing")
|
|
||||||
val dimensions = Starbound.gson.fromJson(frameGrid["dimensions"], Vector2i::class.java) ?: throw IllegalArgumentException("Dimensions are missing")
|
|
||||||
|
|
||||||
require(size.x > 0) { "Invalid texture width of ${size.x}" }
|
|
||||||
require(size.y > 0) { "Invalid texture height of ${size.y}" }
|
|
||||||
|
|
||||||
require(dimensions.x > 0) { "Invalid texture frame count of ${dimensions.x}" }
|
|
||||||
require(dimensions.y > 0) { "Invalid texture stripe count of ${dimensions.y}" }
|
|
||||||
|
|
||||||
val names = frameGrid["names"] as? JsonArray ?: generateFakeNames(dimensions)
|
|
||||||
|
|
||||||
if (names.size() != dimensions.y) {
|
|
||||||
LOGGER.warn("{} inconsistency: it has Y frame span of {}, but {} name strips are defined", texture, dimensions.y, names.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
val frameSets = Object2ObjectArrayMap<String, FrameSetBuilder>()
|
|
||||||
|
|
||||||
for (yPosition in 0 until names.size()) {
|
|
||||||
val list = names[yPosition] as? JsonArray ?: throw IllegalArgumentException("names->$yPosition is not an array")
|
|
||||||
|
|
||||||
if (list.size() != dimensions.x) {
|
|
||||||
LOGGER.warn("{} inconsistency: it has X frame span of {}, but strip at {} has {} names defined", texture, dimensions.x, yPosition, list.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
for (xPosition in 0 until list.size()) {
|
|
||||||
val fullName = list[xPosition]
|
|
||||||
|
|
||||||
if (fullName is JsonNull) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fullName as? JsonPrimitive ?: throw IllegalArgumentException("names->$yPosition->$xPosition: Illegal value $fullName")
|
|
||||||
|
|
||||||
val (setName, frameNumber) = splitName(texture, fullName.asString, true) { "names->$yPosition->$xPosition" }
|
|
||||||
val frameSet = frameSets.computeIfAbsent(setName, ::FrameSetBuilder)
|
|
||||||
|
|
||||||
frameSet.frames[frameNumber] = Frame(
|
|
||||||
texture = texturePath,
|
|
||||||
textureSize = size,
|
|
||||||
texturePosition = Vector2i(x = size.x * xPosition, y = size.y * yPosition))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((newName, originalName) in aliases.entrySet()) {
|
|
||||||
originalName as? JsonPrimitive ?: throw IllegalArgumentException("aliases->$newName: Illegal value $originalName")
|
|
||||||
|
|
||||||
val (oSetName, oFrameNumber) = splitName(texture, originalName.asString, false) { "alias->$newName" }
|
|
||||||
val (nSetName, nFrameNumber) = splitName(texture, newName, true) { "alias->$newName" }
|
|
||||||
|
|
||||||
val oFrameSet = frameSets.computeIfAbsent(oSetName, ::FrameSetBuilder)
|
|
||||||
val nFrameSet = frameSets.computeIfAbsent(nSetName, ::FrameSetBuilder)
|
|
||||||
|
|
||||||
nFrameSet.frames[nFrameNumber] = requireNotNull(oFrameSet.frames[oFrameNumber]) { "alias->$newName points to nothing" }
|
|
||||||
}
|
|
||||||
|
|
||||||
val frameSetList = ImmutableList.builder<FrameSet>()
|
|
||||||
|
|
||||||
for (frameSet in frameSets.values) {
|
|
||||||
frameSetList.add(frameSet.build(texturePath))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResolvedFrameGrid(texturePath, size, dimensions, frameSetList.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun singleFrame(texturePath: String, size: Vector2i = Vector2i.ZERO): IFrameGrid {
|
|
||||||
val frame = Frame(
|
|
||||||
texture = texturePath,
|
|
||||||
textureSize = size,
|
|
||||||
texturePosition = Vector2i.ZERO)
|
|
||||||
|
|
||||||
return ResolvedFrameGrid(
|
|
||||||
texture = texturePath,
|
|
||||||
size = size,
|
|
||||||
dimensions = Vector2i.POSITIVE_XY,
|
|
||||||
frames = listOf(FrameSet(
|
|
||||||
name = "root",
|
|
||||||
frames = listOf(frame),
|
|
||||||
texture = texturePath))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cache = HashMap<String, IFrameGrid>()
|
|
||||||
|
|
||||||
fun loadCached(path: String, weak: Boolean = false, weakSize: Vector2i = Vector2i.ZERO): IFrameGrid {
|
|
||||||
if (path[0] != '/')
|
|
||||||
throw IllegalArgumentException("Path must be absolute")
|
|
||||||
|
|
||||||
val splitPath = path.split('/').toMutableList()
|
|
||||||
val last = splitPath.last()
|
|
||||||
splitPath.removeLast()
|
|
||||||
val splitLast = last.split('.')
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (splitLast.size == 1) {
|
|
||||||
// имя уже абсолютное
|
|
||||||
return cache.computeIfAbsent(path) {
|
|
||||||
val frames = Starbound.locate("$path.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
|
||||||
|
|
||||||
if (weak && frames == null) {
|
|
||||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", path)
|
|
||||||
|
|
||||||
return@computeIfAbsent singleFrame("$path.png", weakSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newPath = "${splitPath.joinToString("/")}/${splitLast[0]}"
|
|
||||||
|
|
||||||
return cache.computeIfAbsent(newPath) {
|
|
||||||
val frames = Starbound.locate("$newPath.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
|
||||||
|
|
||||||
if (weak && frames == null) {
|
|
||||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", newPath)
|
|
||||||
|
|
||||||
return@computeIfAbsent singleFrame("$newPath.png", weakSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, newPath)
|
|
||||||
}
|
|
||||||
} catch (err: Throwable) {
|
|
||||||
throw MalformedFrameGridException("Reading animated texture definition $path", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadFrameStrip(path: String, weak: Boolean = false, weakSize: Vector2i = Vector2i.ZERO): FrameSet {
|
|
||||||
if (path[0] != '/')
|
|
||||||
throw IllegalArgumentException("Path must be absolute")
|
|
||||||
|
|
||||||
val split = path.split(':')
|
|
||||||
|
|
||||||
if (split.size == 1) {
|
|
||||||
// мы хотим получить главный кадр, который является анонимным
|
|
||||||
val load = loadCached(path, weak, weakSize)
|
|
||||||
check(load.frameCount == 1) { "$path has ${load.frameCount} frame strips, but we want exactly one!" }
|
|
||||||
return load[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
val load = loadCached(split[0], weak, weakSize)
|
|
||||||
return load[split[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ResolvedFrameGrid(
|
|
||||||
override val texture: String,
|
|
||||||
override val size: Vector2i,
|
|
||||||
override val dimensions: Vector2i,
|
|
||||||
|
|
||||||
override val frames: List<FrameSet>
|
|
||||||
) : IFrameGrid {
|
|
||||||
override val isResolved = true
|
|
||||||
|
|
||||||
override fun resolve(determinedSize: Vector2i) {
|
|
||||||
require(determinedSize.x > 0) { "Invalid image width ${determinedSize.x}" }
|
|
||||||
require(determinedSize.y > 0) { "Invalid image height ${determinedSize.y}" }
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class LazyFrameGrid(
|
|
||||||
override val texture: String,
|
|
||||||
override val size: Vector2i,
|
|
||||||
override val dimensions: Vector2i,
|
|
||||||
) : IFrameGrid {
|
|
||||||
private var _frames: List<FrameSet>? = null
|
|
||||||
|
|
||||||
override val frames: List<FrameSet>
|
|
||||||
get() = _frames ?: throw IllegalStateException("Call resolve() first")
|
|
||||||
|
|
||||||
override var isResolved = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun resolve(determinedSize: Vector2i) {
|
|
||||||
if (_frames != null)
|
|
||||||
return
|
|
||||||
|
|
||||||
check(dimensions == determinedSize) { "$texture was expected to have dimensions of $dimensions, $determinedSize given" }
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.AssembledPrototype
|
import ru.dbotthepony.kstarbound.defs.AssembledPrototype
|
||||||
import ru.dbotthepony.kstarbound.defs.DamageType
|
import ru.dbotthepony.kstarbound.defs.DamageType
|
||||||
import ru.dbotthepony.kstarbound.defs.FrameSet
|
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.ImageReference
|
import ru.dbotthepony.kstarbound.defs.animation.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController
|
import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController
|
||||||
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
|
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
|
||||||
|
Loading…
Reference in New Issue
Block a user