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: 0.9.0
Show newest version
package ru.casperix.spine.json

import ru.casperix.atlas.Atlas
import ru.casperix.math.Transform
import ru.casperix.math.axis_aligned.float32.Box2f
import ru.casperix.math.color.Color
import ru.casperix.math.color.ColorDecoder
import ru.casperix.math.color.Colors
import ru.casperix.math.color.float32.Color3f
import ru.casperix.math.color.float32.Color4f
import ru.casperix.math.vector.float32.Vector2f
import ru.casperix.spine.*
import ru.casperix.spine.animation.*
import ru.casperix.spine.device.PixelMapRegion
import ru.casperix.spine.json.component.AnimationJson
import ru.casperix.spine.json.component.AttachmentJson
import ru.casperix.spine.json.component.RegionAttachmentJson
import ru.casperix.spine.json.component.SkeletonJson

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 transforms = decodeTransformConstraints(json)
        val defaultSkin = skins.firstOrNull { it.name == "default" }
        return json.skeleton.run {
            val area = Box2f.byDimension(Vector2f(x, y), Vector2f(width, height))
            SkeletonData(
                animations,
                audio,
                bones,
                defaultSkin,
                emptyList(),
                fps,
                hash,
                emptyList(),
                images,
                name,
                emptyList(),
                emptyList(),
                referenceScale,
                skins,
                slots,
                transforms,
                spine,
                area,
            )
        }
    }

    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")

            slotTimeline.attachment?.apply {
                timelineList += AttachmentTimeline(slotData.index, this)
            }

            slotTimeline.alpha?.apply {
                timelineList += AlphaTimeline(slotData.index, this)
            }

            slotTimeline.rgb?.apply {
                timelineList += RGBTimeline(slotData.index, map { RGBKeyFrame(it.time, it.curve, decodeColor3f(it.color)) })
            }

            slotTimeline.rgba?.apply {
                timelineList += RGBATimeline(slotData.index, map { RGBAKeyFrame(it.time, it.curve, decodeColor4f(it.color)) })
            }
        }

        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.translatex?.let { frames ->
                timelineList += TranslateXTimeline(boneData.index, frames)
            }
            boneTimeline.translatey?.let { frames ->
                timelineList += TranslateYTimeline(boneData.index, frames)
            }
            boneTimeline.scale?.let { frames ->
                timelineList += ScaleTimeline(boneData.index, frames)
            }
            boneTimeline.scalex?.let { frames ->
                timelineList += ScaleXTimeline(boneData.index, frames)
            }
            boneTimeline.scaley?.let { frames ->
                timelineList += ScaleYTimeline(boneData.index, frames)
            }
            boneTimeline.shear?.let { frames ->
                timelineList += ShearTimeline(boneData.index, frames)
            }
            boneTimeline.shearx?.let { frames ->
                timelineList += ShearXTimeline(boneData.index, frames)
            }
            boneTimeline.sheary?.let { frames ->
                timelineList += ShearYTimeline(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)
                    ?: return EmptyAttachment(attachmentJson.name)
//                    ?: 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 decodeColor3f(rawColor: String): Color3f {
        val color = decodeColor4f(rawColor)
        return Color3f(color.red, color.green, color.blue)
    }

    private fun decodeColor4f(rawColor: String): Color4f {
        return decodeColor(rawColor).toColor4f()
    }

    private fun decodeColor(rawColor: String): Color {
        val hex = rawColor.removePrefix("0x")
        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.shearX, boneJson.shearY),
                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) decodeColor4f(slotJson.dark) else null

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

    private fun decodeTransformConstraints(json: SkeletonJson): List {
        return json.transform.orEmpty().map { jsonItem ->
            val bones = jsonItem.bones.map { it ->
                boneCache[it] ?: throw Exception("Can't found bone with name: \"$it\"")
            }
            val target = boneCache[jsonItem.target] ?: throw Exception("Can't found target bone")

            jsonItem.run {
                TransformConstraintData(
                    name,
                    order,
                    skin,
                    target,
                    bones,
                    local,
                    relative,
                    Transform(x, y, rotation, scaleX, scaleY),
                    Transform(mixX, mixY, mixRotate, mixScaleX, mixScaleY),
                )
            }
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy