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

commonMain.ru.casperix.spine.json.SkeletonDecoder.kt Maven / Gradle / Ivy

There is a newer version: 1.1.3
Show newest version
package ru.casperix.spine.json

import ru.casperix.math.color.Color
import ru.casperix.math.color.ColorDecoder
import ru.casperix.math.color.Colors
import ru.casperix.misc.sliceSafe
import ru.casperix.atlas.Atlas
import ru.casperix.math.Transform
import ru.casperix.spine.*
import ru.casperix.spine.animation.*
import ru.casperix.spine.device.PixelMapRegion
import ru.casperix.spine.json.component.*

class SkeletonDecoder(val json: SkeletonJson, val name: String, val atlas: Atlas) {
    private val boneCache = mutableMapOf()
    private val slotCache = mutableMapOf()
    private val defaultColor = Colors.WHITE

    val output = decode()

    private fun decode(): SkeletonData {
        val bones = decodeBones(json)
        val slots = decodeSlots(json)
        val animations = decodeAnimations(json)
        val skins = decodeSkins(json)
        val defaultSkin = skins.firstOrNull { it.name == "default" }
        return SkeletonData(
            animations,
            json.skeleton.audio,
            bones,
            defaultSkin,
            emptyList(),
            json.skeleton.fps,
            json.skeleton.hash,
            json.skeleton.height,
            emptyList(),
            json.skeleton.images,
            name,
            emptyList(),
            emptyList(),
            json.skeleton.referenceScale,
            skins,
            slots,
            emptyList(),
            json.skeleton.spine,
            json.skeleton.width,
            json.skeleton.x,
            json.skeleton.y,
        )
    }

    private fun decodeAnimations(json: SkeletonJson): List {
        return json.animations.entries.map { (animationName, animation) ->
            decodeAnimation(json, animationName, animation)
        }
    }

    private fun decodeAnimation(json: SkeletonJson, animationName: String, animation: AnimationJson): Animation {
        val timelineList = mutableListOf()

        animation.slots?.entries?.forEach { (name, slotTimeline) ->
            val slotData = slotCache[name] ?: throw Exception("Slot not found: $name")
        }

        animation.bones?.entries?.forEach { (name, boneTimeline) ->
            val boneData = boneCache[name] ?: throw Exception("Bone not found: $name")
            boneTimeline.rotate?.let { frames ->
                timelineList += RotateTimeline(boneData.index, frames)
            }
            boneTimeline.translate?.let { frames ->
                timelineList += TranslateTimeline(boneData.index, frames)
            }
            boneTimeline.scale?.let { frames ->
                timelineList += ScaleTimeline(boneData.index, frames)
            }
            boneTimeline.shear?.let { frames ->
                timelineList += ShearTimeline(boneData.index, frames)
            }
        }

        return Animation(animationName, timelineList)
    }

    private fun decodeSkins(json: SkeletonJson): List {
        return json.skins.map {
            val attachments = it.attachments.orEmpty().entries.flatMap { (slotName, attachmentMap) ->
                attachmentMap.entries.map { (attachmentName, attachmentJson) ->
                    val slotIndex = slotCache[slotName]?.index ?: -1
                    val key = SkinAttachmentKey(attachmentName, slotIndex)
                    Pair(key, decodeAttachment(attachmentJson, attachmentName))
                }
            }.toMap()
            Skin(it.name, decodeColorOrNull(it.color) ?: defaultColor, attachments, emptyList(), emptyList())
        }
    }

    private fun decodeAttachment(attachmentJson: AttachmentJson, attachmentName: String): Attachment {
        return when (attachmentJson) {
            is RegionAttachmentJson -> {
                val texturePath = if (!attachmentJson.path.isNullOrBlank()) {
                    attachmentJson.path
                } else if (attachmentJson.name.isNotBlank()) {
                    attachmentJson.name
                } else {
                    attachmentName
                }

                val region = getRegion(texturePath)
                    ?: throw Exception("Undefined pixel region: $texturePath")

                RegionAttachment(
                    attachmentName,
                    decodeColor(attachmentJson.color),
                    attachmentJson.path,
                    null,
                    region,
                    attachmentJson.x,
                    attachmentJson.y,
                    attachmentJson.scaleX,
                    attachmentJson.scaleY,
                    attachmentJson.rotation,
                    attachmentJson.width,
                    attachmentJson.height,
                    floatArrayOf(),
                    floatArrayOf(),
                )
            }

            else -> {
                UnknownAttachment(attachmentJson.name)
            }
        }
    }

    private fun getRegion(texturePath: String): PixelMapRegion? {
        atlas.pages.forEach { page ->
            page.regions.firstOrNull { it.name == texturePath }?.let { attributes ->
                return PixelMapRegion(page.pixelMap, attributes)
            }
        }
        return null
    }

    private fun decodeColor(color: String): Color {
        val hex = color.sliceSafe(2 until color.length)
        return ColorDecoder.parseHex(hex)
    }

    private fun decodeColorOrNull(color: String?): Color? {
        if (color == null) return null
        return decodeColor(color)
    }

    private fun decodeBones(json: SkeletonJson): List {
        return json.bones.mapIndexed { index, boneJson ->
            val parent = boneCache.get(boneJson.parent)
            val data = BoneData(
                boneJson.name,
                Transform(boneJson.x, boneJson.y, boneJson.rotation, boneJson.scaleX, boneJson.scaleY, boneJson.schearX, boneJson.schearY),
                parent,
                decodeColor(boneJson.color),
                "",
                index,
                boneJson.inherit,
                boneJson.length,
                boneJson.skin,
                boneJson.visible,
            )
            boneCache[data.name] = data

            data
        }
    }

    private fun decodeSlots(json: SkeletonJson): List {
        return json.slots.mapIndexed { index, slotJson ->
            val bone = boneCache.get(slotJson.bone) ?: throw Exception("Slot bone not found for name: ${slotJson.bone}")
            val dark = if (slotJson.dark != null) decodeColor(slotJson.dark) else null

            slotJson.bone
            val data = SlotData(
                slotJson.attachment,
                slotJson.blend,
                bone,
                decodeColor(slotJson.color),
                dark,
                index,
                slotJson.name,
                "",
                slotJson.visible
            )
            slotCache[data.name] = data
            data
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy