All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ru.casperix.atlas.AtlasLoader.kt Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show 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 - 2024 Weber Informatics LLC | Privacy Policy