![JAR search and dependency download from the Maven repository](/logo.png)
commonMain.ru.casperix.spine.json.SkeletonDecoder.kt Maven / Gradle / Ivy
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