[libgdx] Improved JSON and binary data loading exceptions.

This commit is contained in:
Nathan Sweet 2025-06-01 11:48:31 -04:00
parent b74ba4cab2
commit a48a3d1fba
2 changed files with 365 additions and 344 deletions

View File

@ -180,9 +180,13 @@ public class SkeletonBinary extends SkeletonLoader {
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
SkeletonData skeletonData = readSkeletonData(file.read());
skeletonData.name = file.nameWithoutExtension();
return skeletonData;
try {
SkeletonData skeletonData = readSkeletonData(file.read());
skeletonData.name = file.nameWithoutExtension();
return skeletonData;
} catch (Throwable ex) {
throw new SerializationException("Error reading binary skeleton file: " + file, ex);
}
}
public SkeletonData readSkeletonData (InputStream dataInput) {
@ -192,11 +196,13 @@ public class SkeletonBinary extends SkeletonLoader {
var input = new SkeletonInput(dataInput);
var skeletonData = new SkeletonData();
String version = null;
try {
long hash = input.readLong();
skeletonData.hash = hash == 0 ? null : Long.toString(hash);
skeletonData.version = input.readString();
if (skeletonData.version.isEmpty()) skeletonData.version = null;
version = skeletonData.version;
skeletonData.x = input.readFloat();
skeletonData.y = input.readFloat();
skeletonData.width = input.readFloat();
@ -498,8 +504,9 @@ public class SkeletonBinary extends SkeletonLoader {
for (int i = 0; i < constraintCount; i++)
if (constraints[i] instanceof SliderData data) data.animation = animations[input.readInt(true)];
} catch (IOException ex) {
throw new SerializationException("Error reading skeleton file.", ex);
} catch (Throwable ex) {
if (version != null) throw new SerializationException("Error reading binary skeleton data, version: " + version, ex);
throw new SerializationException("Error binary skeleton data.", ex);
} finally {
try {
input.close();

View File

@ -135,394 +135,408 @@ public class SkeletonJson extends SkeletonLoader {
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
SkeletonData skeletonData = readSkeletonData(new JsonReader().parse(file));
skeletonData.name = file.nameWithoutExtension();
return skeletonData;
try {
SkeletonData skeletonData = readSkeletonData(new JsonReader().parse(file));
skeletonData.name = file.nameWithoutExtension();
return skeletonData;
} catch (Throwable ex) {
throw new SerializationException("Error reading JSON skeleton file: " + file, ex);
}
}
public SkeletonData readSkeletonData (InputStream input) {
if (input == null) throw new IllegalArgumentException("dataInput cannot be null.");
return readSkeletonData(new JsonReader().parse(input));
try {
return readSkeletonData(new JsonReader().parse(input));
} catch (Throwable ex) {
throw new SerializationException("Error reading JSON skeleton data.", ex);
}
}
public SkeletonData readSkeletonData (JsonValue root) {
if (root == null) throw new IllegalArgumentException("root cannot be null.");
float scale = this.scale;
// Skeleton.
var skeletonData = new SkeletonData();
JsonValue skeletonMap = root.get("skeleton");
if (skeletonMap != null) {
skeletonData.hash = skeletonMap.getString("hash", null);
skeletonData.version = skeletonMap.getString("spine", null);
skeletonData.x = skeletonMap.getFloat("x", 0);
skeletonData.y = skeletonMap.getFloat("y", 0);
skeletonData.width = skeletonMap.getFloat("width", 0);
skeletonData.height = skeletonMap.getFloat("height", 0);
skeletonData.referenceScale = skeletonMap.getFloat("referenceScale", 100) * scale;
skeletonData.fps = skeletonMap.getFloat("fps", 30);
skeletonData.imagesPath = skeletonMap.getString("images", null);
skeletonData.audioPath = skeletonMap.getString("audio", null);
}
try {
float scale = this.scale;
// Bones.
for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
BoneData parent = null;
String parentName = boneMap.getString("parent", null);
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null) throw new SerializationException("Parent bone not found: " + parentName);
// Skeleton.
JsonValue skeletonMap = root.get("skeleton");
if (skeletonMap != null) {
skeletonData.hash = skeletonMap.getString("hash", null);
skeletonData.version = skeletonMap.getString("spine", null);
skeletonData.x = skeletonMap.getFloat("x", 0);
skeletonData.y = skeletonMap.getFloat("y", 0);
skeletonData.width = skeletonMap.getFloat("width", 0);
skeletonData.height = skeletonMap.getFloat("height", 0);
skeletonData.referenceScale = skeletonMap.getFloat("referenceScale", 100) * scale;
skeletonData.fps = skeletonMap.getFloat("fps", 30);
skeletonData.imagesPath = skeletonMap.getString("images", null);
skeletonData.audioPath = skeletonMap.getString("audio", null);
}
var data = new BoneData(skeletonData.bones.size, boneMap.getString("name"), parent);
data.length = boneMap.getFloat("length", 0) * scale;
BoneLocal setup = data.setup;
setup.x = boneMap.getFloat("x", 0) * scale;
setup.y = boneMap.getFloat("y", 0) * scale;
setup.rotation = boneMap.getFloat("rotation", 0);
setup.scaleX = boneMap.getFloat("scaleX", 1);
setup.scaleY = boneMap.getFloat("scaleY", 1);
setup.shearX = boneMap.getFloat("shearX", 0);
setup.shearY = boneMap.getFloat("shearY", 0);
setup.inherit = Inherit.valueOf(boneMap.getString("inherit", Inherit.normal.name()));
data.skinRequired = boneMap.getBoolean("skin", false);
String color = boneMap.getString("color", null);
if (color != null) Color.valueOf(color, data.getColor());
data.icon = boneMap.getString("icon", null);
data.visible = boneMap.getBoolean("visible", true);
skeletonData.bones.add(data);
}
// Slots.
for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
String slotName = slotMap.getString("name");
String boneName = slotMap.getString("bone");
BoneData boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName);
var data = new SlotData(skeletonData.slots.size, slotName, boneData);
String color = slotMap.getString("color", null);
if (color != null) Color.valueOf(color, data.setup.getColor());
String dark = slotMap.getString("dark", null);
if (dark != null) data.setup.darkColor = Color.valueOf(dark);
data.attachmentName = slotMap.getString("attachment", null);
data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name()));
data.visible = slotMap.getBoolean("visible", true);
skeletonData.slots.add(data);
}
// Constraints.
for (JsonValue constraintMap = root.getChild("constraints"); constraintMap != null; constraintMap = constraintMap.next) {
String name = constraintMap.getString("name");
boolean skinRequired = constraintMap.getBoolean("skin", false);
switch (constraintMap.getString("type")) {
case "ik" -> {
var data = new IkConstraintData(name);
data.skinRequired = skinRequired;
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("IK bone not found: " + entry);
data.bones.add(bone);
// Bones.
for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
BoneData parent = null;
String parentName = boneMap.getString("parent", null);
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null) throw new SerializationException("Parent bone not found: " + parentName);
}
var data = new BoneData(skeletonData.bones.size, boneMap.getString("name"), parent);
data.length = boneMap.getFloat("length", 0) * scale;
BoneLocal setup = data.setup;
setup.x = boneMap.getFloat("x", 0) * scale;
setup.y = boneMap.getFloat("y", 0) * scale;
setup.rotation = boneMap.getFloat("rotation", 0);
setup.scaleX = boneMap.getFloat("scaleX", 1);
setup.scaleY = boneMap.getFloat("scaleY", 1);
setup.shearX = boneMap.getFloat("shearX", 0);
setup.shearY = boneMap.getFloat("shearY", 0);
setup.inherit = Inherit.valueOf(boneMap.getString("inherit", Inherit.normal.name()));
data.skinRequired = boneMap.getBoolean("skin", false);
String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
String color = boneMap.getString("color", null);
if (color != null) Color.valueOf(color, data.getColor());
data.uniform = constraintMap.getBoolean("uniform", false);
IkConstraintPose setup = data.setup;
setup.mix = constraintMap.getFloat("mix", 1);
setup.softness = constraintMap.getFloat("softness", 0) * scale;
setup.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
setup.compress = constraintMap.getBoolean("compress", false);
setup.stretch = constraintMap.getBoolean("stretch", false);
data.icon = boneMap.getString("icon", null);
data.visible = boneMap.getBoolean("visible", true);
skeletonData.constraints.add(data);
skeletonData.bones.add(data);
}
case "transform" -> {
var data = new TransformConstraintData(name);
data.skinRequired = skinRequired;
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Transform constraint bone not found: " + entry);
data.bones.add(bone);
}
// Slots.
for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
String slotName = slotMap.getString("name");
String boneName = slotMap.getString("bone");
BoneData boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName);
String sourceName = constraintMap.getString("source");
data.source = skeletonData.findBone(sourceName);
if (data.source == null)
throw new SerializationException("Transform constraint source bone not found: " + sourceName);
var data = new SlotData(skeletonData.slots.size, slotName, boneData);
data.localSource = constraintMap.getBoolean("localSource", false);
data.localTarget = constraintMap.getBoolean("localTarget", false);
data.additive = constraintMap.getBoolean("additive", false);
data.clamp = constraintMap.getBoolean("clamp", false);
String color = slotMap.getString("color", null);
if (color != null) Color.valueOf(color, data.setup.getColor());
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
FromProperty from = fromProperty(fromEntry.name);
float fromScale = propertyScale(fromEntry.name, scale);
from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1;
ToProperty to;
switch (toEntry.name) {
case "rotate" -> {
rotate = true;
to = new ToRotate();
}
case "x" -> {
x = true;
to = new ToX();
toScale = scale;
}
case "y" -> {
y = true;
to = new ToY();
toScale = scale;
}
case "scaleX" -> {
scaleX = true;
to = new ToScaleX();
}
case "scaleY" -> {
scaleY = true;
to = new ToScaleY();
}
case "shearY" -> {
shearY = true;
to = new ToShearY();
}
default -> throw new SerializationException("Invalid transform constraint to property: " + toEntry.name);
}
to.offset = toEntry.getFloat("offset", 0) * toScale;
to.max = toEntry.getFloat("max", 1) * toScale;
to.scale = toEntry.getFloat("scale") * toScale / fromScale;
from.to.add(to);
String dark = slotMap.getString("dark", null);
if (dark != null) data.setup.darkColor = Color.valueOf(dark);
data.attachmentName = slotMap.getString("attachment", null);
data.blendMode = BlendMode.valueOf(slotMap.getString("blend", BlendMode.normal.name()));
data.visible = slotMap.getBoolean("visible", true);
skeletonData.slots.add(data);
}
// Constraints.
for (JsonValue constraintMap = root.getChild("constraints"); constraintMap != null; constraintMap = constraintMap.next) {
String name = constraintMap.getString("name");
boolean skinRequired = constraintMap.getBoolean("skin", false);
switch (constraintMap.getString("type")) {
case "ik" -> {
var data = new IkConstraintData(name);
data.skinRequired = skinRequired;
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("IK bone not found: " + entry);
data.bones.add(bone);
}
if (from.to.notEmpty()) data.properties.add(from);
String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
data.uniform = constraintMap.getBoolean("uniform", false);
IkConstraintPose setup = data.setup;
setup.mix = constraintMap.getFloat("mix", 1);
setup.softness = constraintMap.getFloat("softness", 0) * scale;
setup.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
setup.compress = constraintMap.getBoolean("compress", false);
setup.stretch = constraintMap.getBoolean("stretch", false);
skeletonData.constraints.add(data);
}
case "transform" -> {
var data = new TransformConstraintData(name);
data.skinRequired = skinRequired;
data.offsets[0] = constraintMap.getFloat("rotation", 0);
data.offsets[1] = constraintMap.getFloat("x", 0) * scale;
data.offsets[2] = constraintMap.getFloat("y", 0) * scale;
data.offsets[3] = constraintMap.getFloat("scaleX", 0);
data.offsets[4] = constraintMap.getFloat("scaleY", 0);
data.offsets[5] = constraintMap.getFloat("shearY", 0);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Transform constraint bone not found: " + entry);
data.bones.add(bone);
}
TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
if (x) setup.mixX = constraintMap.getFloat("mixX", 1);
if (y) setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
if (scaleX) setup.mixScaleX = constraintMap.getFloat("mixScaleX", 1);
if (scaleY) setup.mixScaleY = constraintMap.getFloat("mixScaleY", setup.mixScaleX);
if (shearY) setup.mixShearY = constraintMap.getFloat("mixShearY", 1);
String sourceName = constraintMap.getString("source");
data.source = skeletonData.findBone(sourceName);
if (data.source == null)
throw new SerializationException("Transform constraint source bone not found: " + sourceName);
skeletonData.constraints.add(data);
}
case "path" -> {
var data = new PathConstraintData(name);
data.skinRequired = skinRequired;
data.localSource = constraintMap.getBoolean("localSource", false);
data.localTarget = constraintMap.getBoolean("localTarget", false);
data.additive = constraintMap.getBoolean("additive", false);
data.clamp = constraintMap.getBoolean("clamp", false);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Path bone not found: " + entry);
data.bones.add(bone);
boolean rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
for (JsonValue fromEntry = constraintMap.getChild("properties"); fromEntry != null; fromEntry = fromEntry.next) {
FromProperty from = fromProperty(fromEntry.name);
float fromScale = propertyScale(fromEntry.name, scale);
from.offset = fromEntry.getFloat("offset", 0) * fromScale;
for (JsonValue toEntry = fromEntry.getChild("to"); toEntry != null; toEntry = toEntry.next) {
float toScale = 1;
ToProperty to;
switch (toEntry.name) {
case "rotate" -> {
rotate = true;
to = new ToRotate();
}
case "x" -> {
x = true;
to = new ToX();
toScale = scale;
}
case "y" -> {
y = true;
to = new ToY();
toScale = scale;
}
case "scaleX" -> {
scaleX = true;
to = new ToScaleX();
}
case "scaleY" -> {
scaleY = true;
to = new ToScaleY();
}
case "shearY" -> {
shearY = true;
to = new ToShearY();
}
default -> throw new SerializationException("Invalid transform constraint to property: " + toEntry.name);
}
to.offset = toEntry.getFloat("offset", 0) * toScale;
to.max = toEntry.getFloat("max", 1) * toScale;
to.scale = toEntry.getFloat("scale") * toScale / fromScale;
from.to.add(to);
}
if (from.to.notEmpty()) data.properties.add(from);
}
data.offsets[0] = constraintMap.getFloat("rotation", 0);
data.offsets[1] = constraintMap.getFloat("x", 0) * scale;
data.offsets[2] = constraintMap.getFloat("y", 0) * scale;
data.offsets[3] = constraintMap.getFloat("scaleX", 0);
data.offsets[4] = constraintMap.getFloat("scaleY", 0);
data.offsets[5] = constraintMap.getFloat("shearY", 0);
TransformConstraintPose setup = data.setup;
if (rotate) setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
if (x) setup.mixX = constraintMap.getFloat("mixX", 1);
if (y) setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
if (scaleX) setup.mixScaleX = constraintMap.getFloat("mixScaleX", 1);
if (scaleY) setup.mixScaleY = constraintMap.getFloat("mixScaleY", setup.mixScaleX);
if (shearY) setup.mixShearY = constraintMap.getFloat("mixShearY", 1);
skeletonData.constraints.add(data);
}
case "path" -> {
var data = new PathConstraintData(name);
data.skinRequired = skinRequired;
String slotName = constraintMap.getString("slot");
data.slot = skeletonData.findSlot(slotName);
if (data.slot == null) throw new SerializationException("Path slot not found: " + slotName);
for (JsonValue entry = constraintMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Path bone not found: " + entry);
data.bones.add(bone);
}
data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));
data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent"));
data.offsetRotation = constraintMap.getFloat("rotation", 0);
PathConstraintPose setup = data.setup;
setup.position = constraintMap.getFloat("position", 0);
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = constraintMap.getFloat("spacing", 0);
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
setup.mixX = constraintMap.getFloat("mixX", 1);
setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
String slotName = constraintMap.getString("slot");
data.slot = skeletonData.findSlot(slotName);
if (data.slot == null) throw new SerializationException("Path slot not found: " + slotName);
skeletonData.constraints.add(data);
}
case "physics" -> {
var data = new PhysicsConstraintData(name);
data.skinRequired = skinRequired;
data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));
data.rotateMode = RotateMode.valueOf(constraintMap.getString("rotateMode", "tangent"));
data.offsetRotation = constraintMap.getFloat("rotation", 0);
PathConstraintPose setup = data.setup;
setup.position = constraintMap.getFloat("position", 0);
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = constraintMap.getFloat("spacing", 0);
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = constraintMap.getFloat("mixRotate", 1);
setup.mixX = constraintMap.getFloat("mixX", 1);
setup.mixY = constraintMap.getFloat("mixY", setup.mixX);
String boneName = constraintMap.getString("bone");
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Physics bone not found: " + boneName);
skeletonData.constraints.add(data);
}
case "physics" -> {
var data = new PhysicsConstraintData(name);
data.skinRequired = skinRequired;
data.x = constraintMap.getFloat("x", 0);
data.y = constraintMap.getFloat("y", 0);
data.rotate = constraintMap.getFloat("rotate", 0);
data.scaleX = constraintMap.getFloat("scaleX", 0);
data.shearX = constraintMap.getFloat("shearX", 0);
data.limit = constraintMap.getFloat("limit", 5000) * scale;
data.step = 1f / constraintMap.getInt("fps", 60);
PhysicsConstraintPose setup = data.setup;
setup.inertia = constraintMap.getFloat("inertia", 0.5f);
setup.strength = constraintMap.getFloat("strength", 100);
setup.damping = constraintMap.getFloat("damping", 0.85f);
setup.massInverse = 1 / constraintMap.getFloat("mass", 1);
setup.wind = constraintMap.getFloat("wind", 0);
setup.gravity = constraintMap.getFloat("gravity", 0);
setup.mix = constraintMap.getFloat("mix", 1);
data.inertiaGlobal = constraintMap.getBoolean("inertiaGlobal", false);
data.strengthGlobal = constraintMap.getBoolean("strengthGlobal", false);
data.dampingGlobal = constraintMap.getBoolean("dampingGlobal", false);
data.massGlobal = constraintMap.getBoolean("massGlobal", false);
data.windGlobal = constraintMap.getBoolean("windGlobal", false);
data.gravityGlobal = constraintMap.getBoolean("gravityGlobal", false);
data.mixGlobal = constraintMap.getBoolean("mixGlobal", false);
skeletonData.constraints.add(data);
}
case "slider" -> {
var data = new SliderData(name);
data.skinRequired = skinRequired;
data.additive = constraintMap.getBoolean("additive", false);
data.loop = constraintMap.getBoolean("loop", false);
data.setup.time = constraintMap.getFloat("time", 0);
data.setup.mix = constraintMap.getFloat("mix", 1);
String boneName = constraintMap.getString("bone", null);
if (boneName != null) {
String boneName = constraintMap.getString("bone");
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Slider bone not found: " + boneName);
String property = constraintMap.getString("property");
data.property = fromProperty(property);
data.property.offset = constraintMap.getFloat("offset", 0) * propertyScale(property, scale);
data.scale = constraintMap.getFloat("scale");
data.local = constraintMap.getBoolean("local", false);
if (data.bone == null) throw new SerializationException("Physics bone not found: " + boneName);
data.x = constraintMap.getFloat("x", 0);
data.y = constraintMap.getFloat("y", 0);
data.rotate = constraintMap.getFloat("rotate", 0);
data.scaleX = constraintMap.getFloat("scaleX", 0);
data.shearX = constraintMap.getFloat("shearX", 0);
data.limit = constraintMap.getFloat("limit", 5000) * scale;
data.step = 1f / constraintMap.getInt("fps", 60);
PhysicsConstraintPose setup = data.setup;
setup.inertia = constraintMap.getFloat("inertia", 0.5f);
setup.strength = constraintMap.getFloat("strength", 100);
setup.damping = constraintMap.getFloat("damping", 0.85f);
setup.massInverse = 1 / constraintMap.getFloat("mass", 1);
setup.wind = constraintMap.getFloat("wind", 0);
setup.gravity = constraintMap.getFloat("gravity", 0);
setup.mix = constraintMap.getFloat("mix", 1);
data.inertiaGlobal = constraintMap.getBoolean("inertiaGlobal", false);
data.strengthGlobal = constraintMap.getBoolean("strengthGlobal", false);
data.dampingGlobal = constraintMap.getBoolean("dampingGlobal", false);
data.massGlobal = constraintMap.getBoolean("massGlobal", false);
data.windGlobal = constraintMap.getBoolean("windGlobal", false);
data.gravityGlobal = constraintMap.getBoolean("gravityGlobal", false);
data.mixGlobal = constraintMap.getBoolean("mixGlobal", false);
skeletonData.constraints.add(data);
}
case "slider" -> {
var data = new SliderData(name);
data.skinRequired = skinRequired;
data.additive = constraintMap.getBoolean("additive", false);
data.loop = constraintMap.getBoolean("loop", false);
data.setup.time = constraintMap.getFloat("time", 0);
data.setup.mix = constraintMap.getFloat("mix", 1);
skeletonData.constraints.add(data);
}
}
}
String boneName = constraintMap.getString("bone", null);
if (boneName != null) {
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SerializationException("Slider bone not found: " + boneName);
String property = constraintMap.getString("property");
data.property = fromProperty(property);
data.property.offset = constraintMap.getFloat("offset", 0) * propertyScale(property, scale);
data.scale = constraintMap.getFloat("scale");
data.local = constraintMap.getBoolean("local", false);
}
// Skins.
for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next) {
var skin = new Skin(skinMap.getString("name"));
for (JsonValue entry = skinMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Skin bone not found: " + entry);
skin.bones.add(bone);
skeletonData.constraints.add(data);
}
}
}
skin.bones.shrink();
for (JsonValue entry = skinMap.getChild("ik"); entry != null; entry = entry.next) {
IkConstraintData constraint = skeletonData.findConstraint(entry.asString(), IkConstraintData.class);
if (constraint == null) throw new SerializationException("Skin IK constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("transform"); entry != null; entry = entry.next) {
TransformConstraintData constraint = skeletonData.findConstraint(entry.asString(), TransformConstraintData.class);
if (constraint == null) throw new SerializationException("Skin transform constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("path"); entry != null; entry = entry.next) {
PathConstraintData constraint = skeletonData.findConstraint(entry.asString(), PathConstraintData.class);
if (constraint == null) throw new SerializationException("Skin path constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("physics"); entry != null; entry = entry.next) {
PhysicsConstraintData constraint = skeletonData.findConstraint(entry.asString(), PhysicsConstraintData.class);
if (constraint == null) throw new SerializationException("Skin physics constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("slider"); entry != null; entry = entry.next) {
SliderData constraint = skeletonData.findConstraint(entry.asString(), SliderData.class);
if (constraint == null) throw new SerializationException("Skin slider not found: " + entry);
skin.constraints.add(constraint);
}
skin.constraints.shrink();
for (JsonValue slotEntry = skinMap.getChild("attachments"); slotEntry != null; slotEntry = slotEntry.next) {
SlotData slot = skeletonData.findSlot(slotEntry.name);
if (slot == null) throw new SerializationException("Slot not found: " + slotEntry.name);
for (JsonValue entry = slotEntry.child; entry != null; entry = entry.next) {
try {
Attachment attachment = readAttachment(entry, skin, slot.index, entry.name, skeletonData);
if (attachment != null) skin.setAttachment(slot.index, entry.name, attachment);
} catch (Throwable ex) {
throw new SerializationException("Error reading attachment: " + entry.name + ", skin: " + skin, ex);
// Skins.
for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next) {
var skin = new Skin(skinMap.getString("name"));
for (JsonValue entry = skinMap.getChild("bones"); entry != null; entry = entry.next) {
BoneData bone = skeletonData.findBone(entry.asString());
if (bone == null) throw new SerializationException("Skin bone not found: " + entry);
skin.bones.add(bone);
}
skin.bones.shrink();
for (JsonValue entry = skinMap.getChild("ik"); entry != null; entry = entry.next) {
IkConstraintData constraint = skeletonData.findConstraint(entry.asString(), IkConstraintData.class);
if (constraint == null) throw new SerializationException("Skin IK constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("transform"); entry != null; entry = entry.next) {
TransformConstraintData constraint = skeletonData.findConstraint(entry.asString(), TransformConstraintData.class);
if (constraint == null) throw new SerializationException("Skin transform constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("path"); entry != null; entry = entry.next) {
PathConstraintData constraint = skeletonData.findConstraint(entry.asString(), PathConstraintData.class);
if (constraint == null) throw new SerializationException("Skin path constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("physics"); entry != null; entry = entry.next) {
PhysicsConstraintData constraint = skeletonData.findConstraint(entry.asString(), PhysicsConstraintData.class);
if (constraint == null) throw new SerializationException("Skin physics constraint not found: " + entry);
skin.constraints.add(constraint);
}
for (JsonValue entry = skinMap.getChild("slider"); entry != null; entry = entry.next) {
SliderData constraint = skeletonData.findConstraint(entry.asString(), SliderData.class);
if (constraint == null) throw new SerializationException("Skin slider not found: " + entry);
skin.constraints.add(constraint);
}
skin.constraints.shrink();
for (JsonValue slotEntry = skinMap.getChild("attachments"); slotEntry != null; slotEntry = slotEntry.next) {
SlotData slot = skeletonData.findSlot(slotEntry.name);
if (slot == null) throw new SerializationException("Slot not found: " + slotEntry.name);
for (JsonValue entry = slotEntry.child; entry != null; entry = entry.next) {
try {
Attachment attachment = readAttachment(entry, skin, slot.index, entry.name, skeletonData);
if (attachment != null) skin.setAttachment(slot.index, entry.name, attachment);
} catch (Throwable ex) {
throw new SerializationException("Error reading attachment: " + entry.name + ", skin: " + skin, ex);
}
}
}
String color = skinMap.getString("color", null);
if (color != null) Color.valueOf(color, skin.getColor());
skeletonData.skins.add(skin);
if (skin.name.equals("default")) skeletonData.defaultSkin = skin;
}
String color = skinMap.getString("color", null);
if (color != null) Color.valueOf(color, skin.getColor());
skeletonData.skins.add(skin);
if (skin.name.equals("default")) skeletonData.defaultSkin = skin;
}
// Linked meshes.
LinkedMesh[] items = linkedMeshes.items;
for (int i = 0, n = linkedMeshes.size; i < n; i++) {
LinkedMesh linkedMesh = items[i];
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
if (linkedMesh.mesh.getRegion() != null) linkedMesh.mesh.updateRegion();
}
linkedMeshes.clear();
// Events.
for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next) {
var data = new EventData(eventMap.name);
data.intValue = eventMap.getInt("int", 0);
data.floatValue = eventMap.getFloat("float", 0f);
data.stringValue = eventMap.getString("string", "");
data.audioPath = eventMap.getString("audio", null);
if (data.audioPath != null) {
data.volume = eventMap.getFloat("volume", 1);
data.balance = eventMap.getFloat("balance", 0);
// Linked meshes.
LinkedMesh[] items = linkedMeshes.items;
for (int i = 0, n = linkedMeshes.size; i < n; i++) {
LinkedMesh linkedMesh = items[i];
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
if (linkedMesh.mesh.getRegion() != null) linkedMesh.mesh.updateRegion();
}
skeletonData.events.add(data);
}
linkedMeshes.clear();
// Animations.
for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next) {
try {
readAnimation(animationMap, animationMap.name, skeletonData);
} catch (Throwable ex) {
throw new SerializationException("Error reading animation: " + animationMap.name, ex);
// Events.
for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next) {
var data = new EventData(eventMap.name);
data.intValue = eventMap.getInt("int", 0);
data.floatValue = eventMap.getFloat("float", 0f);
data.stringValue = eventMap.getString("string", "");
data.audioPath = eventMap.getString("audio", null);
if (data.audioPath != null) {
data.volume = eventMap.getFloat("volume", 1);
data.balance = eventMap.getFloat("balance", 0);
}
skeletonData.events.add(data);
}
}
// Slider animations.
for (JsonValue constraintMap = root.getChild("constraints"); constraintMap != null; constraintMap = constraintMap.next) {
if (constraintMap.getString("type").equals("slider")) {
SliderData data = skeletonData.findConstraint(constraintMap.getString("name"), SliderData.class);
String animationName = constraintMap.getString("animation");
data.animation = skeletonData.findAnimation(animationName);
if (data.animation == null) throw new SerializationException("Slider animation not found: " + animationName);
// Animations.
for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next) {
try {
readAnimation(animationMap, animationMap.name, skeletonData);
} catch (Throwable ex) {
throw new SerializationException("Error reading animation: " + animationMap.name, ex);
}
}
}
skeletonData.bones.shrink();
skeletonData.slots.shrink();
skeletonData.skins.shrink();
skeletonData.events.shrink();
skeletonData.animations.shrink();
skeletonData.constraints.shrink();
return skeletonData;
// Slider animations.
for (JsonValue constraintMap = root.getChild("constraints"); constraintMap != null; constraintMap = constraintMap.next) {
if (constraintMap.getString("type").equals("slider")) {
SliderData data = skeletonData.findConstraint(constraintMap.getString("name"), SliderData.class);
String animationName = constraintMap.getString("animation");
data.animation = skeletonData.findAnimation(animationName);
if (data.animation == null) throw new SerializationException("Slider animation not found: " + animationName);
}
}
skeletonData.bones.shrink();
skeletonData.slots.shrink();
skeletonData.skins.shrink();
skeletonData.events.shrink();
skeletonData.animations.shrink();
skeletonData.constraints.shrink();
return skeletonData;
} catch (Throwable ex) {
if (skeletonData.version != null)
throw new SerializationException("Error reading JSON skeleton data, version: " + skeletonData.version, ex);
throw new SerializationException("Error JSON skeleton data.", ex);
}
}
private FromProperty fromProperty (String type) {