commonMain.ru.casperix.spine.json.SkeletonDecoder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spine-jvm Show documentation
Show all versions of spine-jvm Show documentation
Signals for all occasions
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),
)
}
}
}
}