commonMain.ru.casperix.atlas.AtlasLoader.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spine Show documentation
Show all versions of spine Show documentation
Signals for all occasions
The newest version!
package ru.casperix.atlas
import ru.casperix.math.axis_aligned.int32.Box2i
import ru.casperix.math.axis_aligned.int32.Dimension2i
import ru.casperix.math.vector.int32.Vector2i
import ru.casperix.misc.sliceSafe
import ru.casperix.renderer.pixel_map.PixelMap
import ru.casperix.signals.concrete.EitherFuture
import ru.casperix.signals.concrete.EitherSignal
import ru.casperix.atlas.PathUtil.pathParent
import ru.casperix.file_data.BlockData
import ru.casperix.file_data.FileData
import ru.casperix.file_data.FileDataLoader
import ru.casperix.multiplatform.loader.ResourceLoadError
import ru.casperix.multiplatform.loader.resourceLoader
object AtlasLoader {
private val cache = mutableMapOf>()
class PagePreview(
val info: PageProperties,
val regions: List,
)
class AtlasPageError(val path: String, val error: ResourceLoadError) : ResourceLoadError
fun load(path: String): EitherFuture {
return cache.getOrPut(path) {
loadDirect(path)
}
}
fun loadDirect(path: String): EitherFuture {
val parentPath = pathParent(path)
val atlasLoader = FileDataLoader.load(path)
val signal = EitherSignal()
atlasLoader.thenAccept {
val previewList = parse(it)
val map = mutableMapOf()
fun checkAccept() {
if (map.size == previewList.size) {
val pageList = map.entries.map { (preview, pixelMap) ->
Page(pixelMap, preview.info, preview.regions)
}
signal.accept(Atlas(pageList))
}
}
previewList.forEach { preview ->
val imagePath = listOfNotNull(parentPath, preview.info.name).joinToString(PathUtil.separator.toString())
resourceLoader.loadImage(imagePath).then({
map[preview] = it
checkAccept()
}, {
signal.reject(AtlasPageError(preview.info.name, it))
})
}
}
atlasLoader.thenReject {
signal.reject(it)
}
return signal
}
private fun parse(file: FileData): List {
return file.pageList.mapNotNull {
val title = it.blockList.firstOrNull() ?: return@mapNotNull null
val page = parseAsPageProperties(title)
val regions = it.blockList.sliceSafe(1..it.blockList.size).map {
parseAsRegion(it)
}
PagePreview(page, regions)
}
}
private fun parseAsRegion(data: BlockData): RegionAttributes = data.run {
return RegionAttributes(
name,
values["index"]?.toInt() ?: 0,
parseBounds(values["bounds"]),
parseInts(values["offsets"]),
parseRotate(values["rotate"]),
parseInts(values["split"]),
parseInts(values["pads"]),
)
}
private fun parseRotate(rawRotate: String?): OrthogonalRotate = if (rawRotate == null || rawRotate == "false") {
OrthogonalRotate.ZERO
} else if (rawRotate == "true") {
OrthogonalRotate.ONE
} else {
val raw = rawRotate.toInt().mod(360)
val rawIndex = raw / 90
if (rawIndex * 90 != raw) throw Exception("Invalid rotate: $rawRotate")
OrthogonalRotate.entries.getOrNull(rawIndex) ?: throw Exception("Invalid rotate: $rawRotate")
}
private fun parseInts(value: String?): List {
if (value == null) return emptyList()
return value.split(Regex("[,;]\\s*")).map {
it.toInt()
}
}
private fun parseSize(value: String?): Dimension2i {
val i = parseInts(value)
if (i.size == 2) {
return Dimension2i(i[0], i[1])
} else {
throw Exception("Can't parse size from: $value")
}
}
private fun parseBounds(value: String?): Box2i {
val i = parseInts(value)
if (i.size == 4) {
return Box2i.byDimension(Vector2i(i[0], i[1]), Vector2i(i[2], i[3]))
} else {
throw Exception("Can't parse bounds from: $value")
}
}
private fun parseAsPageProperties(data: BlockData): PageProperties = data.run {
val filters = values["filter"]?.split(", ").orEmpty()
return PageProperties(
name,
parseSize(values["size"]),
values["format"] ?: "",
filters.getOrNull(0) ?: "Nearest",
filters.getOrNull(1) ?: "Nearest",
values["repeat"] ?: "",
values["pma"]?.toBoolean() ?: false,
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy