#![allow(
clippy::too_many_lines,
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_lossless,
clippy::cast_sign_loss,
clippy::many_single_char_names,
clippy::match_same_arms,
clippy::needless_pass_by_ref_mut,
clippy::unused_self,
clippy::assigning_clones,
clippy::doc_markdown,
clippy::missing_panics_doc,
clippy::too_many_arguments,
clippy::collapsible_if,
clippy::collapsible_match,
clippy::items_after_statements,
clippy::map_unwrap_or,
clippy::needless_pass_by_value,
clippy::type_complexity,
clippy::unnecessary_wraps
)]
use serde_json::Value;
use thiserror::Error;
use crate::animation::{BEZIER_SIZE, compute_bezier_samples};
use crate::data::attachment::{Attachment, Sequence, VertexData};
use std::sync::Arc;
use crate::data::{
Animation, AnimationEvent, AnimationId, AttachmentId, AttachmentRef, BlendMode, BoneData,
BoneId, ConstraintData, ConstraintId, CurveFrames, EventData, EventId, FromProperty,
IkConstraintData, Inherit, PathConstraintData, PhysicsConstraintData, PhysicsProperty,
PositionMode, RotateMode, ScaleYMode, SkeletonData, Skin, SkinId, SliderData, SliderProperty,
SlotData, SlotId, SpacingMode, Timeline, ToProperty, TransformConstraintData,
TransformProperty,
};
use crate::load::AttachmentLoader;
use crate::load::AttachmentLoaderError;
use crate::load::binary::timeline_duration;
use crate::math::Color;
pub const TARGET_VERSION: &str = "4.3";
const CURVE_LINEAR: f32 = 0.0;
const CURVE_STEPPED: f32 = 1.0;
const CURVE_BEZIER: f32 = 2.0;
#[derive(Debug, Error)]
pub enum JsonError {
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("skeleton version mismatch: file reports {found:?}, runtime targets {expected:?}")]
UnsupportedVersion { found: String, expected: String },
#[error("missing required field {path:?}")]
MissingField { path: String },
#[error("field {path:?} has unexpected type: {message}")]
BadType { path: String, message: String },
#[error("unknown {entity} value: {value:?}")]
UnknownValue { entity: &'static str, value: String },
#[error("named entity not found: {entity} {name:?}")]
NotFound { entity: &'static str, name: String },
#[error("invalid color string {value:?}: {message}")]
InvalidColor { value: String, message: String },
#[error("attachment loader error: {0}")]
AttachmentLoader(#[from] AttachmentLoaderError),
}
struct LinkedMesh {
mesh: AttachmentId,
skin_name: Option<String>,
slot: SlotId,
source_slot: SlotId,
source: String,
inherit_timelines: bool,
}
pub struct SkeletonJson<'loader> {
loader: &'loader mut dyn AttachmentLoader,
scale: f32,
linked_meshes: Vec<LinkedMesh>,
}
impl<'loader> SkeletonJson<'loader> {
pub fn with_loader(loader: &'loader mut dyn AttachmentLoader) -> Self {
Self {
loader,
scale: 1.0,
linked_meshes: Vec::new(),
}
}
#[must_use]
pub fn with_scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
pub fn read_slice(self, bytes: &[u8]) -> Result<SkeletonData, JsonError> {
let root: Value = serde_json::from_slice(bytes)?;
self.read_value(root)
}
pub fn read_str(self, json: &str) -> Result<SkeletonData, JsonError> {
let root: Value = serde_json::from_str(json)?;
self.read_value(root)
}
pub fn read_value(mut self, root: Value) -> Result<SkeletonData, JsonError> {
let mut sd = SkeletonData::default();
self.linked_meshes.clear();
if let Some(sk) = root.get("skeleton") {
sd.hash = get_str(sk, "hash").unwrap_or("").to_string();
sd.version = get_str(sk, "spine").unwrap_or("").to_string();
if !sd.version.is_empty() && !sd.version.starts_with(TARGET_VERSION) {
return Err(JsonError::UnsupportedVersion {
found: sd.version.clone(),
expected: TARGET_VERSION.to_string(),
});
}
sd.x = get_f32(sk, "x", 0.0);
sd.y = get_f32(sk, "y", 0.0);
sd.width = get_f32(sk, "width", 0.0);
sd.height = get_f32(sk, "height", 0.0);
sd.reference_scale = get_f32(sk, "referenceScale", 100.0) * self.scale;
sd.fps = get_f32(sk, "fps", 30.0);
sd.audio_path = get_str(sk, "audio").unwrap_or("").to_string();
sd.images_path = get_str(sk, "images").unwrap_or("").to_string();
}
if let Some(bones) = root.get("bones").and_then(Value::as_array) {
sd.bones.reserve(bones.len());
for (i, bone) in bones.iter().enumerate() {
let name = get_str(bone, "name").unwrap_or("").to_string();
let parent = match get_str(bone, "parent") {
Some(p) => Some(find_bone(&sd, p, "parent bone")?),
None => None,
};
let mut b = BoneData::new(BoneId(i as u16), name, parent);
b.length = get_f32(bone, "length", 0.0) * self.scale;
let setup = &mut b.setup;
setup.x = get_f32(bone, "x", 0.0) * self.scale;
setup.y = get_f32(bone, "y", 0.0) * self.scale;
setup.rotation = get_f32(bone, "rotation", 0.0);
setup.scale_x = get_f32(bone, "scaleX", 1.0);
setup.scale_y = get_f32(bone, "scaleY", 1.0);
setup.shear_x = get_f32(bone, "shearX", 0.0);
setup.shear_y = get_f32(bone, "shearY", 0.0);
setup.inherit = parse_inherit(get_str(bone, "inherit").unwrap_or("normal"))?;
b.skin_required = get_bool(bone, "skin", false);
if let Some(color) = get_str(bone, "color") {
b.color = parse_color(color, true)?;
}
b.icon = get_str(bone, "icon").unwrap_or("").to_string();
b.icon_size = get_f32(bone, "iconSize", 1.0);
b.icon_rotation = get_f32(bone, "iconRotation", 0.0);
b.visible = get_bool(bone, "visible", true);
sd.bones.push(b);
}
}
if let Some(slots) = root.get("slots").and_then(Value::as_array) {
sd.slots.reserve(slots.len());
for (i, slot) in slots.iter().enumerate() {
let name = get_str(slot, "name").unwrap_or("").to_string();
let bone_name = get_str(slot, "bone").ok_or_else(|| JsonError::MissingField {
path: format!("slots[{i}].bone"),
})?;
let bone = find_bone(&sd, bone_name, "slot bone")?;
let mut s = SlotData::new(SlotId(i as u16), name, bone);
if let Some(c) = get_str(slot, "color") {
s.color = parse_color(c, true)?;
}
if let Some(d) = get_str(slot, "dark") {
let mut dc = parse_color(d, false)?;
dc.a = 1.0;
s.dark_color = Some(dc);
}
if let Some(att) = get_str(slot, "attachment") {
s.attachment_name = Some(att.to_string());
}
if let Some(blend) = get_str(slot, "blend") {
s.blend_mode = match blend {
"normal" => BlendMode::Normal,
"additive" => BlendMode::Additive,
"multiply" => BlendMode::Multiply,
"screen" => BlendMode::Screen,
other => {
return Err(JsonError::UnknownValue {
entity: "blend mode",
value: other.to_string(),
});
}
};
}
s.visible = get_bool(slot, "visible", true);
sd.slots.push(s);
}
}
if let Some(constraints) = root.get("constraints").and_then(Value::as_array) {
sd.constraints.reserve(constraints.len());
for c in constraints {
let constraint = self.read_constraint(c, &sd)?;
sd.constraints.push(constraint);
}
}
if let Some(skins) = root.get("skins").and_then(Value::as_array) {
sd.skins.reserve(skins.len());
for skin_map in skins {
self.read_skin(skin_map, &mut sd)?;
}
}
self.resolve_linked_meshes(&mut sd)?;
if let Some(events) = root.get("events").and_then(Value::as_object) {
sd.events.reserve(events.len());
for (i, (name, e)) in events.iter().enumerate() {
let mut data = EventData::new(EventId(i as u16), name.clone());
data.int_value = get_int(e, "int", 0);
data.float_value = get_f32(e, "float", 0.0);
data.string_value = get_str(e, "string").unwrap_or("").to_string();
data.audio_path = get_str(e, "audio").unwrap_or("").to_string();
if !data.audio_path.is_empty() {
data.volume = get_f32(e, "volume", 1.0);
data.balance = get_f32(e, "balance", 0.0);
}
sd.events.push(data);
}
}
if let Some(anims) = root.get("animations").and_then(Value::as_object) {
sd.animations.reserve(anims.len());
for (name, a) in anims {
let anim = self.read_animation(name, a, &sd)?;
sd.animations.push(anim);
}
}
if let Some(constraints) = root.get("constraints").and_then(Value::as_array) {
for (i, c) in constraints.iter().enumerate() {
if get_str(c, "type") != Some("slider") {
continue;
}
let anim_name = get_str(c, "animation").unwrap_or("");
let anim = sd
.animations
.iter()
.position(|a| a.name == anim_name)
.ok_or_else(|| JsonError::NotFound {
entity: "slider animation",
name: anim_name.to_string(),
})?;
if let ConstraintData::Slider(slider) = &mut sd.constraints[i] {
slider.animation = Some(AnimationId(anim as u16));
}
}
}
sd.intern_attachment_keys();
Ok(sd)
}
fn read_constraint(&self, c: &Value, sd: &SkeletonData) -> Result<ConstraintData, JsonError> {
let scale = self.scale;
let name = get_str(c, "name").unwrap_or("").to_string();
let skin_required = get_bool(c, "skin", false);
let bones = |sd: &SkeletonData| -> Result<Vec<BoneId>, JsonError> {
c.get("bones")
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice)
.iter()
.map(|b| find_bone(sd, b.as_str().unwrap_or(""), "constraint bone"))
.collect()
};
let ty = get_str(c, "type").unwrap_or("");
Ok(match ty {
"ik" => {
let target = find_bone(sd, get_str(c, "target").unwrap_or(""), "IK target bone")?;
let mut data = IkConstraintData::new(name, target);
data.skin_required = skin_required;
data.bones = bones(sd)?;
if let Some(mode) = get_str(c, "scaleY") {
data.scale_y_mode = parse_scale_y_mode(mode)?;
}
let setup = &mut data.setup;
setup.mix = get_f32(c, "mix", 1.0);
setup.softness = get_f32(c, "softness", 0.0) * scale;
setup.bend_direction = if get_bool(c, "bendPositive", true) {
1
} else {
-1
};
setup.compress = get_bool(c, "compress", false);
setup.stretch = get_bool(c, "stretch", false);
ConstraintData::Ik(data)
}
"transform" => {
let source = find_bone(
sd,
get_str(c, "source").unwrap_or(""),
"transform source bone",
)?;
let mut data = TransformConstraintData::new(name, source);
data.skin_required = skin_required;
data.bones = bones(sd)?;
data.local_source = get_bool(c, "localSource", false);
data.local_target = get_bool(c, "localTarget", false);
data.additive = get_bool(c, "additive", false);
data.clamp = get_bool(c, "clamp", false);
let mut keyed = [false; 6];
if let Some(props) = c.get("properties").and_then(Value::as_object) {
for (from_name, from_entry) in props {
let from = parse_property(from_name)?;
let from_scale = property_scale(from, scale);
let mut to = Vec::new();
if let Some(tos) = from_entry.get("to").and_then(Value::as_object) {
for (to_name, to_entry) in tos {
let property = parse_property(to_name)?;
keyed[property.offset_index()] = true;
let to_scale = property_scale(property, scale);
to.push(ToProperty {
property,
offset: get_f32(to_entry, "offset", 0.0) * to_scale,
max: get_f32(to_entry, "max", 1.0) * to_scale,
scale: get_f32(to_entry, "scale", 1.0) * to_scale / from_scale,
});
}
}
if !to.is_empty() {
data.properties.push(FromProperty {
property: from,
offset: get_f32(from_entry, "offset", 0.0) * from_scale,
to,
});
}
}
}
data.offsets = [
get_f32(c, "rotation", 0.0),
get_f32(c, "x", 0.0) * scale,
get_f32(c, "y", 0.0) * scale,
get_f32(c, "scaleX", 0.0),
get_f32(c, "scaleY", 0.0),
get_f32(c, "shearY", 0.0),
];
let setup = &mut data.setup;
if keyed[0] {
setup.mix_rotate = get_f32(c, "mixRotate", 1.0);
}
if keyed[1] {
setup.mix_x = get_f32(c, "mixX", 1.0);
}
if keyed[2] {
setup.mix_y = get_f32(c, "mixY", setup.mix_x);
}
if keyed[3] {
setup.mix_scale_x = get_f32(c, "mixScaleX", 1.0);
}
if keyed[4] {
setup.mix_scale_y = get_f32(c, "mixScaleY", setup.mix_scale_x);
}
if keyed[5] {
setup.mix_shear_y = get_f32(c, "mixShearY", 1.0);
}
ConstraintData::Transform(data)
}
"path" => {
let slot = find_slot(sd, get_str(c, "slot").unwrap_or(""), "path slot")?;
let mut data = PathConstraintData::new(name, slot);
data.skin_required = skin_required;
data.bones = bones(sd)?;
data.position_mode = match get_str(c, "positionMode").unwrap_or("percent") {
"fixed" => PositionMode::Fixed,
"percent" => PositionMode::Percent,
other => {
return Err(JsonError::UnknownValue {
entity: "position mode",
value: other.to_string(),
});
}
};
data.spacing_mode = match get_str(c, "spacingMode").unwrap_or("length") {
"length" => SpacingMode::Length,
"fixed" => SpacingMode::Fixed,
"percent" => SpacingMode::Percent,
"proportional" => SpacingMode::Proportional,
other => {
return Err(JsonError::UnknownValue {
entity: "spacing mode",
value: other.to_string(),
});
}
};
data.rotate_mode = match get_str(c, "rotateMode").unwrap_or("tangent") {
"tangent" => RotateMode::Tangent,
"chain" => RotateMode::Chain,
"chainScale" => RotateMode::ChainScale,
other => {
return Err(JsonError::UnknownValue {
entity: "rotate mode",
value: other.to_string(),
});
}
};
data.offset_rotation = get_f32(c, "rotation", 0.0);
let setup = &mut data.setup;
setup.position = get_f32(c, "position", 0.0);
if data.position_mode == PositionMode::Fixed {
setup.position *= scale;
}
setup.spacing = get_f32(c, "spacing", 0.0);
if matches!(data.spacing_mode, SpacingMode::Length | SpacingMode::Fixed) {
setup.spacing *= scale;
}
setup.mix_rotate = get_f32(c, "mixRotate", 1.0);
setup.mix_x = get_f32(c, "mixX", 1.0);
setup.mix_y = get_f32(c, "mixY", setup.mix_x);
ConstraintData::Path(data)
}
"physics" => {
let bone = find_bone(sd, get_str(c, "bone").unwrap_or(""), "physics bone")?;
let mut data = PhysicsConstraintData::new(name, bone);
data.skin_required = skin_required;
data.x = get_f32(c, "x", 0.0);
data.y = get_f32(c, "y", 0.0);
data.rotate = get_f32(c, "rotate", 0.0);
data.scale_x = get_f32(c, "scaleX", 0.0);
if let Some(mode) = get_str(c, "scaleY") {
data.scale_y_mode = parse_scale_y_mode(mode)?;
}
data.shear_x = get_f32(c, "shearX", 0.0);
data.limit = get_f32(c, "limit", 5000.0) * scale;
data.step = 1.0 / get_int(c, "fps", 60) as f32;
let setup = &mut data.setup;
setup.inertia = get_f32(c, "inertia", 0.5);
setup.strength = get_f32(c, "strength", 100.0);
setup.damping = get_f32(c, "damping", 0.85);
setup.mass_inverse = 1.0 / get_f32(c, "mass", 1.0);
setup.wind = get_f32(c, "wind", 0.0);
setup.gravity = get_f32(c, "gravity", 0.0);
setup.mix = get_f32(c, "mix", 1.0);
data.inertia_global = get_bool(c, "inertiaGlobal", false);
data.strength_global = get_bool(c, "strengthGlobal", false);
data.damping_global = get_bool(c, "dampingGlobal", false);
data.mass_global = get_bool(c, "massGlobal", false);
data.wind_global = get_bool(c, "windGlobal", false);
data.gravity_global = get_bool(c, "gravityGlobal", false);
data.mix_global = get_bool(c, "mixGlobal", false);
ConstraintData::Physics(data)
}
"slider" => {
let mut data = SliderData::new(name);
data.skin_required = skin_required;
data.additive = get_bool(c, "additive", false);
data.looping = get_bool(c, "loop", false);
data.setup.mix = get_f32(c, "mix", 1.0);
if let Some(bone_name) = get_str(c, "bone") {
data.bone = Some(find_bone(sd, bone_name, "slider bone")?);
let property = parse_property(get_str(c, "property").unwrap_or(""))?;
let property_scale = property_scale(property, scale);
data.property = Some(SliderProperty {
property,
offset: get_f32(c, "from", 0.0) * property_scale,
});
data.offset = get_f32(c, "to", 0.0);
data.scale = get_f32(c, "scale", 1.0) / property_scale;
data.max = get_f32(c, "max", 0.0);
data.local = get_bool(c, "local", false);
} else {
data.setup.time = get_f32(c, "time", 0.0);
}
ConstraintData::Slider(data)
}
other => {
return Err(JsonError::UnknownValue {
entity: "constraint type",
value: other.to_string(),
});
}
})
}
fn read_skin(&mut self, skin_map: &Value, sd: &mut SkeletonData) -> Result<(), JsonError> {
let skin_name = get_str(skin_map, "name").unwrap_or("").to_string();
let mut skin = Skin::new(skin_name.clone());
if let Some(color) = get_str(skin_map, "color") {
skin.color = parse_color(color, true)?;
}
for b in skin_map
.get("bones")
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice)
{
skin.bones
.push(find_bone(sd, b.as_str().unwrap_or(""), "skin bone")?);
}
for key in ["ik", "transform", "path", "physics", "slider"] {
for c in skin_map
.get(key)
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice)
{
let name = c.as_str().unwrap_or("");
let id = sd
.find_constraint(name)
.ok_or_else(|| JsonError::NotFound {
entity: "skin constraint",
name: name.to_string(),
})?;
skin.constraints.push(id);
}
}
if let Some(atts) = skin_map.get("attachments").and_then(Value::as_object) {
for (slot_name, slot_obj) in atts {
let slot = find_slot(sd, slot_name, "attachment slot")?;
let slot_obj = slot_obj.as_object().ok_or_else(|| JsonError::BadType {
path: format!("skins.{skin_name}.attachments.{slot_name}"),
message: "expected object".to_string(),
})?;
for (placeholder, att_map) in slot_obj {
if let Some(attachment) =
self.read_attachment(att_map, &skin_name, slot, placeholder, sd)?
{
let id = AttachmentId(sd.attachments.len() as u32);
sd.attachments.push(attachment);
let key = sd.skin_keys.intern(slot, placeholder);
skin.set(key, AttachmentRef::Data(id));
}
}
}
}
if skin_name == "default" {
sd.default_skin = Some(SkinId(sd.skins.len() as u16));
}
sd.skins.push(Arc::new(skin));
Ok(())
}
fn read_attachment(
&mut self,
map: &Value,
skin_name: &str,
slot: SlotId,
placeholder: &str,
sd: &SkeletonData,
) -> Result<Option<Attachment>, JsonError> {
let scale = self.scale;
let name = get_str(map, "name").unwrap_or(placeholder).to_string();
Ok(match get_str(map, "type").unwrap_or("region") {
"region" => {
let path = get_str(map, "path").unwrap_or(&name).to_string();
let sequence = read_sequence(map.get("sequence"));
let Some(mut region) = self.loader.new_region_attachment(
skin_name,
placeholder,
&name,
&path,
sequence,
)?
else {
return Ok(None);
};
region.path = path;
region.x = get_f32(map, "x", 0.0) * scale;
region.y = get_f32(map, "y", 0.0) * scale;
region.scale_x = get_f32(map, "scaleX", 1.0);
region.scale_y = get_f32(map, "scaleY", 1.0);
region.rotation = get_f32(map, "rotation", 0.0);
region.width = get_f32(map, "width", 0.0) * scale;
region.height = get_f32(map, "height", 0.0) * scale;
if let Some(color) = get_str(map, "color") {
region.color = parse_color(color, true)?;
}
region.update_sequence();
Some(Attachment::Region(region))
}
"boundingbox" => {
let Some(mut bb) =
self.loader
.new_bounding_box_attachment(skin_name, placeholder, &name)?
else {
return Ok(None);
};
bb.vertex_data =
self.read_vertices(map, get_int(map, "vertexCount", 0) as usize * 2)?;
if let Some(color) = get_str(map, "color") {
bb.color = parse_color(color, true)?;
}
Some(Attachment::BoundingBox(bb))
}
"mesh" | "linkedmesh" => {
let path = get_str(map, "path").unwrap_or(&name).to_string();
let sequence = read_sequence(map.get("sequence"));
let Some(mut mesh) = self.loader.new_mesh_attachment(
skin_name,
placeholder,
&name,
&path,
sequence,
)?
else {
return Ok(None);
};
mesh.path = path;
if let Some(color) = get_str(map, "color") {
mesh.color = parse_color(color, true)?;
}
mesh.width = get_f32(map, "width", 0.0) * scale;
mesh.height = get_f32(map, "height", 0.0) * scale;
if let Some(source) = get_str(map, "source") {
let source_slot = match get_str(map, "slot") {
Some(s) => find_slot(sd, s, "source mesh slot")?,
None => slot,
};
self.linked_meshes.push(LinkedMesh {
mesh: AttachmentId(sd.attachments.len() as u32),
skin_name: get_str(map, "skin").map(str::to_string),
slot,
source_slot,
source: source.to_string(),
inherit_timelines: get_bool(map, "timelines", true),
});
return Ok(Some(Attachment::Mesh(mesh)));
}
let uvs = f32_array(map.get("uvs"));
mesh.vertex_data = self.read_vertices(map, uvs.len())?;
mesh.triangles = map
.get("triangles")
.and_then(Value::as_array)
.map(|a| a.iter().map(|v| v.as_i64().unwrap_or(0) as u16).collect())
.unwrap_or_default();
mesh.region_uvs = uvs;
mesh.hull_length = (get_int(map, "hull", 0) as u32) << 1;
if let Some(edges) = map.get("edges").and_then(Value::as_array) {
mesh.edges = edges
.iter()
.map(|v| v.as_i64().unwrap_or(0) as u16)
.collect();
}
mesh.update_sequence();
Some(Attachment::Mesh(mesh))
}
"path" => {
let Some(mut path) =
self.loader
.new_path_attachment(skin_name, placeholder, &name)?
else {
return Ok(None);
};
path.closed = get_bool(map, "closed", false);
path.constant_speed = get_bool(map, "constantSpeed", true);
let vertex_count = get_int(map, "vertexCount", 0) as usize;
path.vertex_data = self.read_vertices(map, vertex_count * 2)?;
path.lengths = f32_array(map.get("lengths"))
.into_iter()
.map(|l| l * scale)
.collect();
if let Some(color) = get_str(map, "color") {
path.color = parse_color(color, true)?;
}
Some(Attachment::Path(path))
}
"point" => {
let Some(mut point) =
self.loader
.new_point_attachment(skin_name, placeholder, &name)?
else {
return Ok(None);
};
point.x = get_f32(map, "x", 0.0) * scale;
point.y = get_f32(map, "y", 0.0) * scale;
point.rotation = get_f32(map, "rotation", 0.0);
if let Some(color) = get_str(map, "color") {
point.color = parse_color(color, true)?;
}
Some(Attachment::Point(point))
}
"clipping" => {
let Some(mut clip) =
self.loader
.new_clipping_attachment(skin_name, placeholder, &name)?
else {
return Ok(None);
};
if let Some(end) = get_str(map, "end") {
clip.end_slot = Some(find_slot(sd, end, "clipping end slot")?);
}
clip.convex = get_bool(map, "convex", false);
clip.inverse = get_bool(map, "inverse", false);
clip.vertex_data =
self.read_vertices(map, get_int(map, "vertexCount", 0) as usize * 2)?;
if let Some(color) = get_str(map, "color") {
clip.color = parse_color(color, true)?;
}
Some(Attachment::Clipping(clip))
}
other => {
return Err(JsonError::UnknownValue {
entity: "attachment type",
value: other.to_string(),
});
}
})
}
fn read_vertices(&self, map: &Value, vertices_length: usize) -> Result<VertexData, JsonError> {
let raw = map
.get("vertices")
.and_then(Value::as_array)
.ok_or_else(|| JsonError::MissingField {
path: "attachment.vertices".to_string(),
})?;
let flat: Vec<f32> = raw
.iter()
.map(|v| v.as_f64().unwrap_or(0.0) as f32)
.collect();
let mut vd = VertexData {
world_vertices_length: vertices_length as u32,
..VertexData::default()
};
if vertices_length == flat.len() {
vd.vertices = flat;
if self.scale != 1.0 {
for v in &mut vd.vertices {
*v *= self.scale;
}
}
return Ok(vd);
}
let mut i = 0usize;
while i < flat.len() {
let bone_count = flat[i] as usize;
vd.bones.push(bone_count as i32);
i += 1;
let end = (i + bone_count * 4).min(flat.len());
while i < end {
vd.bones.push(flat[i] as i32);
vd.vertices.push(flat[i + 1] * self.scale);
vd.vertices.push(flat[i + 2] * self.scale);
vd.vertices.push(flat[i + 3]);
i += 4;
}
}
Ok(vd)
}
fn resolve_linked_meshes(&mut self, sd: &mut SkeletonData) -> Result<(), JsonError> {
for lm in std::mem::take(&mut self.linked_meshes) {
let skin_id = match lm.skin_name.as_deref() {
None | Some("") => sd.default_skin,
Some(name) => sd
.skins
.iter()
.position(|s| s.name == name)
.map(|i| SkinId(i as u16)),
}
.ok_or_else(|| JsonError::NotFound {
entity: "linked mesh skin",
name: lm.skin_name.clone().unwrap_or_default(),
})?;
let source = sd
.skin_attachment(skin_id, lm.source_slot, &lm.source)
.ok_or_else(|| JsonError::NotFound {
entity: "linked mesh source",
name: lm.source.clone(),
})?;
crate::load::binary::link_mesh(sd, lm.mesh, source, lm.inherit_timelines);
if lm.inherit_timelines
&& lm.slot != lm.source_slot
&& let Some(link) = sd.attachments[source.index()].timeline_link_mut()
&& !link.slots.contains(&lm.slot)
{
link.slots.push(lm.slot);
}
}
Ok(())
}
fn read_animation(
&self,
name: &str,
root: &Value,
sd: &SkeletonData,
) -> Result<Animation, JsonError> {
let mut anim = Animation::new(name.to_string(), 0.0);
if let Some(slots) = root.get("slots").and_then(Value::as_object) {
for (slot_name, slot_timelines) in slots {
let slot_idx = sd
.slots
.iter()
.position(|s| s.name == *slot_name)
.ok_or_else(|| JsonError::NotFound {
entity: "slot",
name: slot_name.clone(),
})?;
let slot = SlotId(slot_idx as u16);
for (tname, tm) in slot_timelines
.as_object()
.ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.slots.{slot_name}"),
message: "expected object".to_string(),
})?
{
let keys = tm.as_array().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.slots.{slot_name}.{tname}"),
message: "expected array".to_string(),
})?;
match tname.as_str() {
"attachment" => {
let mut frames = Vec::with_capacity(keys.len());
let mut names = Vec::with_capacity(keys.len());
for k in keys {
frames.push(get_f32(k, "time", 0.0));
names.push(get_str(k, "name").map(str::to_string));
}
anim.timelines.push(Timeline::Attachment {
slot,
frames,
names,
keys: Vec::new(),
});
}
"rgba" => {
let curves = read_color_timeline_json(keys, 4, "color", true)?;
anim.timelines.push(Timeline::Rgba { slot, curves });
}
"rgb" => {
let curves = read_color_timeline_json(keys, 3, "color", false)?;
anim.timelines.push(Timeline::Rgb { slot, curves });
}
"alpha" => {
let curves = read_alpha_timeline_json(keys)?;
anim.timelines.push(Timeline::Alpha { slot, curves });
}
"rgba2" => {
let curves = read_rgb2_timeline_json(keys, true)?;
anim.timelines.push(Timeline::Rgba2 { slot, curves });
}
"rgb2" => {
let curves = read_rgb2_timeline_json(keys, false)?;
anim.timelines.push(Timeline::Rgb2 { slot, curves });
}
other => {
return Err(JsonError::UnknownValue {
entity: "slot timeline",
value: other.to_string(),
});
}
}
}
}
}
if let Some(bones) = root.get("bones").and_then(Value::as_object) {
for (bone_name, bone_timelines) in bones {
let bone_idx = sd
.bones
.iter()
.position(|b| b.name == *bone_name)
.ok_or_else(|| JsonError::NotFound {
entity: "bone",
name: bone_name.clone(),
})?;
let bone = BoneId(bone_idx as u16);
anim.bones.push(bone);
for (tname, tm) in bone_timelines
.as_object()
.ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.bones.{bone_name}"),
message: "expected object".to_string(),
})?
{
let keys = tm.as_array().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.bones.{bone_name}.{tname}"),
message: "expected array".to_string(),
})?;
if keys.is_empty() {
continue;
}
match tname.as_str() {
"rotate" => {
let curves = read_timeline1(keys, "value", 0.0, 1.0)?;
anim.timelines.push(Timeline::Rotate { bone, curves });
}
"translate" => {
let curves = read_timeline2(keys, "x", "y", 0.0, self.scale)?;
anim.timelines.push(Timeline::Translate { bone, curves });
}
"translatex" => {
let curves = read_timeline1(keys, "value", 0.0, self.scale)?;
anim.timelines.push(Timeline::TranslateX { bone, curves });
}
"translatey" => {
let curves = read_timeline1(keys, "value", 0.0, self.scale)?;
anim.timelines.push(Timeline::TranslateY { bone, curves });
}
"scale" => {
let curves = read_timeline2(keys, "x", "y", 1.0, 1.0)?;
anim.timelines.push(Timeline::Scale { bone, curves });
}
"scalex" => {
let curves = read_timeline1(keys, "value", 1.0, 1.0)?;
anim.timelines.push(Timeline::ScaleX { bone, curves });
}
"scaley" => {
let curves = read_timeline1(keys, "value", 1.0, 1.0)?;
anim.timelines.push(Timeline::ScaleY { bone, curves });
}
"shear" => {
let curves = read_timeline2(keys, "x", "y", 0.0, 1.0)?;
anim.timelines.push(Timeline::Shear { bone, curves });
}
"shearx" => {
let curves = read_timeline1(keys, "value", 0.0, 1.0)?;
anim.timelines.push(Timeline::ShearX { bone, curves });
}
"sheary" => {
let curves = read_timeline1(keys, "value", 0.0, 1.0)?;
anim.timelines.push(Timeline::ShearY { bone, curves });
}
"inherit" => {
let mut frames = Vec::with_capacity(keys.len());
let mut inherits = Vec::with_capacity(keys.len());
for k in keys {
frames.push(get_f32(k, "time", 0.0));
inherits.push(parse_inherit(
get_str(k, "inherit").unwrap_or("normal"),
)?);
}
anim.timelines.push(Timeline::Inherit {
bone,
frames,
inherits,
});
}
other => {
return Err(JsonError::UnknownValue {
entity: "bone timeline",
value: other.to_string(),
});
}
}
}
}
}
if let Some(ik) = root.get("ik").and_then(Value::as_object) {
for (cname, keys_val) in ik {
let keys = keys_val.as_array().map_or(&[][..], Vec::as_slice);
if keys.is_empty() {
continue;
}
let constraint =
find_constraint(sd, cname, "IK constraint", |c| c.as_ik().is_some())?;
let curves = read_ik_timeline_json(keys, self.scale)?;
anim.timelines
.push(Timeline::IkConstraint { constraint, curves });
}
}
if let Some(tc) = root.get("transform").and_then(Value::as_object) {
for (cname, keys_val) in tc {
let keys = keys_val.as_array().map_or(&[][..], Vec::as_slice);
if keys.is_empty() {
continue;
}
let constraint = find_constraint(sd, cname, "transform constraint", |c| {
c.as_transform().is_some()
})?;
let curves = read_transform_timeline_json(keys)?;
anim.timelines
.push(Timeline::TransformConstraint { constraint, curves });
}
}
if let Some(paths) = root.get("path").and_then(Value::as_object) {
for (cname, sub) in paths {
let constraint =
find_constraint(sd, cname, "path constraint", |c| c.as_path().is_some())?;
let data = sd.constraints[constraint.index()]
.as_path()
.expect("checked");
let tmaps = sub.as_object().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.path.{cname}"),
message: "expected object".to_string(),
})?;
for (tname, keys_val) in tmaps {
let keys = keys_val.as_array().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.path.{cname}.{tname}"),
message: "expected array".to_string(),
})?;
if keys.is_empty() {
continue;
}
match tname.as_str() {
"position" => {
let scale = if data.position_mode == PositionMode::Fixed {
self.scale
} else {
1.0
};
let curves = read_timeline1(keys, "value", 0.0, scale)?;
anim.timelines
.push(Timeline::PathConstraintPosition { constraint, curves });
}
"spacing" => {
let scale = if matches!(
data.spacing_mode,
SpacingMode::Length | SpacingMode::Fixed
) {
self.scale
} else {
1.0
};
let curves = read_timeline1(keys, "value", 0.0, scale)?;
anim.timelines
.push(Timeline::PathConstraintSpacing { constraint, curves });
}
"mix" => {
let curves = read_path_mix_timeline_json(keys)?;
anim.timelines
.push(Timeline::PathConstraintMix { constraint, curves });
}
_ => {}
}
}
}
}
if let Some(phys) = root.get("physics").and_then(Value::as_object) {
for (cname, sub) in phys {
let constraint = if cname.is_empty() {
None
} else {
Some(find_constraint(sd, cname, "physics constraint", |c| {
c.as_physics().is_some()
})?)
};
let tmaps = sub.as_object().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.physics.{cname}"),
message: "expected object".to_string(),
})?;
for (tname, keys_val) in tmaps {
let keys = keys_val.as_array().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.physics.{cname}.{tname}"),
message: "expected array".to_string(),
})?;
if keys.is_empty() {
continue;
}
if tname == "reset" {
let frames = keys
.iter()
.map(|k| get_f32(k, "time", 0.0))
.collect::<Vec<_>>();
anim.timelines
.push(Timeline::PhysicsReset { constraint, frames });
continue;
}
let (property, default) = match tname.as_str() {
"inertia" => (PhysicsProperty::Inertia, 0.0),
"strength" => (PhysicsProperty::Strength, 0.0),
"damping" => (PhysicsProperty::Damping, 0.0),
"mass" => (PhysicsProperty::Mass, 0.0),
"wind" => (PhysicsProperty::Wind, 0.0),
"gravity" => (PhysicsProperty::Gravity, 0.0),
"mix" => (PhysicsProperty::Mix, 1.0),
_ => continue,
};
let curves = read_timeline1(keys, "value", default, 1.0)?;
anim.timelines.push(Timeline::Physics {
constraint,
property,
curves,
});
}
}
}
if let Some(sliders) = root.get("slider").and_then(Value::as_object) {
for (cname, sub) in sliders {
let constraint = find_constraint(sd, cname, "slider", |c| c.as_slider().is_some())?;
for (tname, keys_val) in sub.as_object().into_iter().flatten() {
let keys = keys_val.as_array().map_or(&[][..], Vec::as_slice);
if keys.is_empty() {
continue;
}
let curves = read_timeline1(keys, "value", 1.0, 1.0)?;
match tname.as_str() {
"time" => anim.timelines.push(Timeline::Slider { constraint, curves }),
"mix" => anim
.timelines
.push(Timeline::SliderMix { constraint, curves }),
_ => {}
}
}
}
}
if let Some(skins) = root.get("attachments").and_then(Value::as_object) {
for (skin_name, slots_obj) in skins {
let skin = sd
.skins
.iter()
.position(|s| s.name == *skin_name)
.map(|i| SkinId(i as u16))
.ok_or_else(|| JsonError::NotFound {
entity: "animation skin",
name: skin_name.clone(),
})?;
let slots = slots_obj.as_object().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.attachments.{skin_name}"),
message: "expected object".to_string(),
})?;
for (slot_name, atts) in slots {
let slot_idx = sd
.slots
.iter()
.position(|s| s.name == *slot_name)
.ok_or_else(|| JsonError::NotFound {
entity: "animation slot",
name: slot_name.clone(),
})?;
let slot = SlotId(slot_idx as u16);
let att_map = atts.as_object().ok_or_else(|| JsonError::BadType {
path: format!("animations.{name}.attachments.{skin_name}.{slot_name}"),
message: "expected object".to_string(),
})?;
for (att_name, tmap) in att_map {
let attachment_id =
sd.skin_attachment(skin, slot, att_name).ok_or_else(|| {
JsonError::NotFound {
entity: "animation attachment",
name: att_name.clone(),
}
})?;
let tobj = tmap.as_object().ok_or_else(|| JsonError::BadType {
path: format!(
"animations.{name}.attachments.{skin_name}.{slot_name}.{att_name}"
),
message: "expected object".to_string(),
})?;
for (tname, keys_val) in tobj {
let keys = keys_val.as_array().ok_or_else(|| JsonError::BadType {
path: format!(
"animations.{name}.attachments.{skin_name}.{slot_name}.{att_name}.{tname}"
),
message: "expected array".to_string(),
})?;
if keys.is_empty() {
continue;
}
match tname.as_str() {
"deform" => {
let (weighted, setup) = deform_context(sd, attachment_id);
let deform_length = deform_frame_len(sd, attachment_id);
let (frames, curves, vertices) = read_deform_timeline_json(
keys,
deform_length,
self.scale,
weighted,
&setup,
)?;
anim.timelines.push(Timeline::Deform {
slot,
attachment: attachment_id,
curves: CurveFrames { frames, curves },
vertices,
});
}
"sequence" => {
let mut frames = Vec::with_capacity(keys.len() * 3);
let mut last_delay = 0.0_f32;
for k in keys {
let time = get_f32(k, "time", 0.0);
let delay = get_f32(k, "delay", last_delay);
last_delay = delay;
let index = get_int(k, "index", 0);
let mode_str = get_str(k, "mode").unwrap_or("hold");
let mode = match mode_str {
"hold" => 0u32,
"once" => 1,
"loop" => 2,
"pingpong" => 3,
"onceReverse" => 4,
"loopReverse" => 5,
"pingpongReverse" => 6,
other => {
return Err(JsonError::UnknownValue {
entity: "sequence mode",
value: other.to_string(),
});
}
};
let packed = ((index << 4) as u32 | mode) as i32;
frames.push(time);
frames.push(packed as f32);
frames.push(delay);
}
anim.timelines.push(Timeline::Sequence {
slot,
attachment: attachment_id,
frames,
});
}
_ => {}
}
}
}
}
}
}
if let Some(keys) = root.get("drawOrder").and_then(Value::as_array) {
let mut frames = Vec::with_capacity(keys.len());
let mut draw_orders = Vec::with_capacity(keys.len());
for k in keys {
frames.push(get_f32(k, "time", 0.0));
draw_orders.push(
read_draw_order(sd, k, sd.slots.len(), None)?
.map(|o| o.into_iter().map(|i| SlotId(i as u16)).collect()),
);
}
anim.timelines.push(Timeline::DrawOrder {
frames,
draw_orders,
});
}
for folder in root
.get("drawOrderFolder")
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice)
{
let slots = folder
.get("slots")
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice)
.iter()
.map(|s| find_slot(sd, s.as_str().unwrap_or(""), "draw order folder slot"))
.collect::<Result<Vec<_>, _>>()?;
let keys = folder
.get("keys")
.and_then(Value::as_array)
.map_or(&[][..], Vec::as_slice);
let mut frames = Vec::with_capacity(keys.len());
let mut draw_orders = Vec::with_capacity(keys.len());
for k in keys {
frames.push(get_f32(k, "time", 0.0));
draw_orders.push(
read_draw_order(sd, k, slots.len(), Some(&slots))?
.map(|o| o.into_iter().map(|i| i as u16).collect()),
);
}
anim.timelines.push(Timeline::DrawOrderFolder {
slots,
frames,
draw_orders,
});
}
if let Some(events) = root.get("events").and_then(Value::as_array) {
let mut frames = Vec::with_capacity(events.len());
let mut out = Vec::with_capacity(events.len());
for k in events {
let ename = get_str(k, "name").unwrap_or("");
let ei = sd
.events
.iter()
.position(|e| e.name == ename)
.ok_or_else(|| JsonError::NotFound {
entity: "event",
name: ename.to_string(),
})?;
let data = &sd.events[ei];
let time = get_f32(k, "time", 0.0);
let int_value = get_int(k, "int", data.int_value);
let float_value = get_f32(k, "float", data.float_value);
let string_value: Option<std::sync::Arc<str>> =
Some(get_str(k, "string").unwrap_or(&data.string_value).into());
let (volume, balance) = if data.audio_path.is_empty() {
(data.volume, data.balance)
} else {
(get_f32(k, "volume", 1.0), get_f32(k, "balance", 0.0))
};
frames.push(time);
out.push(AnimationEvent {
time,
event: EventId(ei as u16),
int_value,
float_value,
string_value,
volume,
balance,
});
}
anim.timelines.push(Timeline::Event {
frames,
events: out,
});
}
let timelines = std::mem::take(&mut anim.timelines);
anim.set_timelines(timelines, &sd.attachments);
anim.duration = timeline_duration(&anim);
if let Some(color) = get_str(root, "color") {
anim.color = parse_color(color, true)?;
}
Ok(anim)
}
}
fn get_str<'a>(v: &'a Value, key: &str) -> Option<&'a str> {
v.get(key).and_then(Value::as_str)
}
fn get_f32(v: &Value, key: &str, default: f32) -> f32 {
v.get(key)
.and_then(Value::as_f64)
.map(|f| f as f32)
.unwrap_or(default)
}
fn get_int(v: &Value, key: &str, default: i32) -> i32 {
v.get(key)
.and_then(|x| x.as_i64().or_else(|| x.as_f64().map(|f| f as i64)))
.map(|i| i as i32)
.unwrap_or(default)
}
fn get_bool(v: &Value, key: &str, default: bool) -> bool {
v.get(key)
.and_then(|x| x.as_bool().or_else(|| x.as_i64().map(|n| n != 0)))
.unwrap_or(default)
}
fn find_bone(sd: &SkeletonData, name: &str, entity: &'static str) -> Result<BoneId, JsonError> {
sd.bones
.iter()
.position(|b| b.name == name)
.map(|i| BoneId(i as u16))
.ok_or_else(|| JsonError::NotFound {
entity,
name: name.to_string(),
})
}
fn find_slot(sd: &SkeletonData, name: &str, entity: &'static str) -> Result<SlotId, JsonError> {
sd.slots
.iter()
.position(|s| s.name == name)
.map(|i| SlotId(i as u16))
.ok_or_else(|| JsonError::NotFound {
entity,
name: name.to_string(),
})
}
fn find_constraint(
sd: &SkeletonData,
name: &str,
entity: &'static str,
is_kind: impl Fn(&ConstraintData) -> bool,
) -> Result<ConstraintId, JsonError> {
sd.find_constraint(name)
.filter(|id| is_kind(&sd.constraints[id.index()]))
.ok_or_else(|| JsonError::NotFound {
entity,
name: name.to_string(),
})
}
fn read_draw_order(
sd: &SkeletonData,
key: &Value,
slot_count: usize,
folder: Option<&[SlotId]>,
) -> Result<Option<Vec<i32>>, JsonError> {
let Some(changes) = key.get("offsets").and_then(Value::as_array) else {
return Ok(None);
};
let mut draw_order = vec![-1i32; slot_count];
let mut unchanged = vec![0i32; slot_count.saturating_sub(changes.len())];
let (mut original_index, mut unchanged_index) = (0i32, 0usize);
for change in changes {
let slot_name = get_str(change, "slot").unwrap_or("");
let slot = find_slot(sd, slot_name, "draw order slot")?;
let index = match folder {
None => slot.index() as i32,
Some(f) => f
.iter()
.position(|s| *s == slot)
.ok_or_else(|| JsonError::NotFound {
entity: "draw order folder slot",
name: slot_name.to_string(),
})? as i32,
};
while original_index != index {
unchanged[unchanged_index] = original_index;
unchanged_index += 1;
original_index += 1;
}
draw_order[(original_index + get_int(change, "offset", 0)) as usize] = original_index;
original_index += 1;
}
while (original_index as usize) < slot_count {
unchanged[unchanged_index] = original_index;
unchanged_index += 1;
original_index += 1;
}
for i in (0..slot_count).rev() {
if draw_order[i] == -1 {
unchanged_index -= 1;
draw_order[i] = unchanged[unchanged_index];
}
}
Ok(Some(draw_order))
}
fn f32_array(v: Option<&Value>) -> Vec<f32> {
v.and_then(Value::as_array)
.map(|a| a.iter().map(|x| x.as_f64().unwrap_or(0.0) as f32).collect())
.unwrap_or_default()
}
fn parse_property(s: &str) -> Result<TransformProperty, JsonError> {
Ok(match s {
"rotate" => TransformProperty::Rotate,
"x" => TransformProperty::X,
"y" => TransformProperty::Y,
"scaleX" => TransformProperty::ScaleX,
"scaleY" => TransformProperty::ScaleY,
"shearY" => TransformProperty::ShearY,
other => {
return Err(JsonError::UnknownValue {
entity: "transform property",
value: other.to_string(),
});
}
})
}
fn property_scale(property: TransformProperty, scale: f32) -> f32 {
match property {
TransformProperty::X | TransformProperty::Y => scale,
_ => 1.0,
}
}
fn parse_scale_y_mode(s: &str) -> Result<ScaleYMode, JsonError> {
Ok(match s.to_ascii_lowercase().as_str() {
"none" => ScaleYMode::None,
"uniform" => ScaleYMode::Uniform,
"volume" => ScaleYMode::Volume,
_ => {
return Err(JsonError::UnknownValue {
entity: "scale Y mode",
value: s.to_string(),
});
}
})
}
fn parse_inherit(s: &str) -> Result<Inherit, JsonError> {
Ok(match s {
"normal" => Inherit::Normal,
"onlyTranslation" => Inherit::OnlyTranslation,
"noRotationOrReflection" => Inherit::NoRotationOrReflection,
"noScale" => Inherit::NoScale,
"noScaleOrReflection" => Inherit::NoScaleOrReflection,
other => {
return Err(JsonError::UnknownValue {
entity: "inherit",
value: other.to_string(),
});
}
})
}
fn parse_color(s: &str, has_alpha: bool) -> Result<Color, JsonError> {
fn component(src: &str, i: usize) -> Result<f32, JsonError> {
let start = i * 2;
let slice = src
.get(start..start + 2)
.ok_or_else(|| JsonError::InvalidColor {
value: src.to_string(),
message: format!("missing pair at offset {start}"),
})?;
u32::from_str_radix(slice, 16)
.map(|x| x as f32 / 255.0)
.map_err(|_| JsonError::InvalidColor {
value: src.to_string(),
message: format!("invalid hex pair {slice:?}"),
})
}
let r = component(s, 0)?;
let g = component(s, 1)?;
let b = component(s, 2)?;
let a = if has_alpha { component(s, 3)? } else { 1.0 };
Ok(Color::new(r, g, b, a))
}
fn read_sequence(v: Option<&Value>) -> Sequence {
let Some(item) = v else {
return Sequence::new(1, false);
};
let mut seq = Sequence::new(get_int(item, "count", 0).max(0) as usize, true);
seq.start = get_int(item, "start", 1);
seq.digits = get_int(item, "digits", 0);
seq.setup_index = get_int(item, "setup", 0);
seq
}
fn curve_segment(curve: &Value, value_index: usize) -> Option<(f32, f32, f32, f32)> {
let arr = curve.as_array()?;
let base = value_index * 4;
if arr.len() < base + 4 {
return None;
}
let cx1 = arr.get(base)?.as_f64()? as f32;
let cy1 = arr.get(base + 1)?.as_f64()? as f32;
let cx2 = arr.get(base + 2)?.as_f64()? as f32;
let cy2 = arr.get(base + 3)?.as_f64()? as f32;
Some((cx1, cy1, cx2, cy2))
}
fn set_linear(curves: &mut [f32], frame: usize) {
curves[frame] = CURVE_LINEAR;
}
fn set_stepped(curves: &mut [f32], frame: usize) {
curves[frame] = CURVE_STEPPED;
}
fn set_bezier_sample(
curves: &mut [f32],
frame_count: usize,
frame: usize,
value_index: usize,
bezier_seg_idx: usize,
samples: [f32; BEZIER_SIZE],
) {
if value_index == 0 {
let first_channel_abs = frame_count + bezier_seg_idx * BEZIER_SIZE;
curves[frame] = CURVE_BEZIER + first_channel_abs as f32;
}
let dst = frame_count + (bezier_seg_idx + value_index) * BEZIER_SIZE;
curves[dst..dst + BEZIER_SIZE].copy_from_slice(&samples);
}
fn read_timeline1(
keys: &[Value],
value_key: &str,
default: f32,
scale: f32,
) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let (mut frames, mut curves, mut bezier_count) = (
Vec::with_capacity(frame_count * 2),
Vec::<f32>::new(),
0usize,
);
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 1;
}
}
}
curves.resize(frame_count + bezier_count * BEZIER_SIZE, 0.0);
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let mut time = get_f32(&keys[0], "time", 0.0);
let mut value = get_f32(&keys[0], value_key, default) * scale;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.push(value);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let value2 = get_f32(next, value_key, default) * scale;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
let (cx1, cy1, cx2, cy2) =
curve_segment(v, 0).unwrap_or((time, value, time2, value2));
let samples = compute_bezier_samples(
time,
value,
cx1,
cy1 * scale,
cx2,
cy2 * scale,
time2,
value2,
);
set_bezier_sample(&mut curves, frame_count, frame, 0, bezier_seg_idx, samples);
bezier_seg_idx += 1;
}
}
time = time2;
value = value2;
}
Ok(CurveFrames { frames, curves })
}
fn read_timeline2(
keys: &[Value],
name1: &str,
name2: &str,
default: f32,
scale: f32,
) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 2;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * 3);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let mut time = get_f32(&keys[0], "time", 0.0);
let mut v1 = get_f32(&keys[0], name1, default) * scale;
let mut v2 = get_f32(&keys[0], name2, default) * scale;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.push(v1);
frames.push(v2);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let nv1 = get_f32(next, name1, default) * scale;
let nv2 = get_f32(next, name2, default) * scale;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
for (k_idx, (val1, val2)) in [(v1, nv1), (v2, nv2)].iter().enumerate() {
let (cx1, cy1, cx2, cy2) =
curve_segment(v, k_idx).unwrap_or((time, *val1, time2, *val2));
let samples = compute_bezier_samples(
time,
*val1,
cx1,
cy1 * scale,
cx2,
cy2 * scale,
time2,
*val2,
);
set_bezier_sample(
&mut curves,
frame_count,
frame,
k_idx,
bezier_seg_idx,
samples,
);
}
bezier_seg_idx += 2;
}
}
time = time2;
v1 = nv1;
v2 = nv2;
}
Ok(CurveFrames { frames, curves })
}
fn read_color_timeline_json(
keys: &[Value],
channels: usize,
color_field: &str,
has_alpha: bool,
) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += channels;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * (1 + channels));
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let parse_colors = |k: &Value| -> Result<Vec<f32>, JsonError> {
let s = get_str(k, color_field).unwrap_or("");
let c = parse_color(s, has_alpha)?;
let mut out = vec![c.r, c.g, c.b];
if channels == 4 {
out.push(c.a);
}
Ok(out)
};
let mut time = get_f32(&keys[0], "time", 0.0);
let mut values = parse_colors(&keys[0])?;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.extend_from_slice(&values);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let values2 = parse_colors(next)?;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
for k_idx in 0..channels {
let (cx1, cy1, cx2, cy2) = curve_segment(v, k_idx).unwrap_or((
time,
values[k_idx],
time2,
values2[k_idx],
));
let samples = compute_bezier_samples(
time,
values[k_idx],
cx1,
cy1,
cx2,
cy2,
time2,
values2[k_idx],
);
set_bezier_sample(
&mut curves,
frame_count,
frame,
k_idx,
bezier_seg_idx,
samples,
);
}
bezier_seg_idx += channels;
}
}
time = time2;
values = values2;
}
Ok(CurveFrames { frames, curves })
}
fn read_alpha_timeline_json(keys: &[Value]) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 1;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * 2);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
fn alpha_of(k: &Value) -> Result<f32, JsonError> {
if let Some(v) = k.get("value").and_then(Value::as_f64) {
return Ok(v as f32);
}
if let Some(s) = get_str(k, "color") {
let byte = u32::from_str_radix(s.get(0..2).unwrap_or("00"), 16).map_err(|_| {
JsonError::InvalidColor {
value: s.to_string(),
message: "invalid alpha hex".to_string(),
}
})?;
return Ok(byte as f32 / 255.0);
}
Ok(0.0)
}
let mut time = get_f32(&keys[0], "time", 0.0);
let mut value = alpha_of(&keys[0])?;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.push(value);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let value2 = alpha_of(next)?;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
let (cx1, cy1, cx2, cy2) =
curve_segment(v, 0).unwrap_or((time, value, time2, value2));
let samples =
compute_bezier_samples(time, value, cx1, cy1, cx2, cy2, time2, value2);
set_bezier_sample(&mut curves, frame_count, frame, 0, bezier_seg_idx, samples);
bezier_seg_idx += 1;
}
}
time = time2;
value = value2;
}
Ok(CurveFrames { frames, curves })
}
fn read_rgb2_timeline_json(keys: &[Value], has_alpha: bool) -> Result<CurveFrames, JsonError> {
let channels = if has_alpha { 7 } else { 6 };
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += channels;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * (1 + channels));
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let parse = |k: &Value| -> Result<Vec<f32>, JsonError> {
let light = parse_color(get_str(k, "light").unwrap_or(""), has_alpha)?;
let dark = parse_color(get_str(k, "dark").unwrap_or(""), false)?;
let mut v = vec![light.r, light.g, light.b];
if has_alpha {
v.push(light.a);
}
v.extend_from_slice(&[dark.r, dark.g, dark.b]);
Ok(v)
};
let mut time = get_f32(&keys[0], "time", 0.0);
let mut values = parse(&keys[0])?;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.extend_from_slice(&values);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let values2 = parse(next)?;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
for k_idx in 0..channels {
let (cx1, cy1, cx2, cy2) = curve_segment(v, k_idx).unwrap_or((
time,
values[k_idx],
time2,
values2[k_idx],
));
let samples = compute_bezier_samples(
time,
values[k_idx],
cx1,
cy1,
cx2,
cy2,
time2,
values2[k_idx],
);
set_bezier_sample(
&mut curves,
frame_count,
frame,
k_idx,
bezier_seg_idx,
samples,
);
}
bezier_seg_idx += channels;
}
}
time = time2;
values = values2;
}
Ok(CurveFrames { frames, curves })
}
fn read_ik_timeline_json(keys: &[Value], scale: f32) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 2;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * 6);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let mut time = get_f32(&keys[0], "time", 0.0);
let mut mix = get_f32(&keys[0], "mix", 1.0);
let mut softness = get_f32(&keys[0], "softness", 0.0) * scale;
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
let bend = if get_bool(&keys[frame], "bendPositive", true) {
1.0
} else {
-1.0
};
let compress = if get_bool(&keys[frame], "compress", false) {
1.0
} else {
0.0
};
let stretch = if get_bool(&keys[frame], "stretch", false) {
1.0
} else {
0.0
};
frames.push(time);
frames.push(mix);
frames.push(softness);
frames.push(bend);
frames.push(compress);
frames.push(stretch);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let mix2 = get_f32(next, "mix", 1.0);
let softness2 = get_f32(next, "softness", 0.0) * scale;
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
let (cx1, cy1, cx2, cy2) = curve_segment(v, 0).unwrap_or((time, mix, time2, mix2));
let s0 = compute_bezier_samples(time, mix, cx1, cy1, cx2, cy2, time2, mix2);
set_bezier_sample(&mut curves, frame_count, frame, 0, bezier_seg_idx, s0);
let (cx1, cy1, cx2, cy2) =
curve_segment(v, 1).unwrap_or((time, softness, time2, softness2));
let s1 = compute_bezier_samples(
time,
softness,
cx1,
cy1 * scale,
cx2,
cy2 * scale,
time2,
softness2,
);
set_bezier_sample(&mut curves, frame_count, frame, 1, bezier_seg_idx, s1);
bezier_seg_idx += 2;
}
}
time = time2;
mix = mix2;
softness = softness2;
}
Ok(CurveFrames { frames, curves })
}
fn read_transform_timeline_json(keys: &[Value]) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 6;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * 7);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let parse = |k: &Value| {
let mix_rotate = get_f32(k, "mixRotate", 1.0);
let mix_shear_y = get_f32(k, "mixShearY", 1.0);
let mix_x = get_f32(k, "mixX", 1.0);
let mix_y = get_f32(k, "mixY", mix_x);
let mix_scale_x = get_f32(k, "mixScaleX", 1.0);
let mix_scale_y = get_f32(k, "mixScaleY", mix_scale_x);
[
mix_rotate,
mix_x,
mix_y,
mix_scale_x,
mix_scale_y,
mix_shear_y,
]
};
let mut time = get_f32(&keys[0], "time", 0.0);
let mut values = parse(&keys[0]);
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.extend_from_slice(&values);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let values2 = parse(next);
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
for k_idx in 0..6 {
let (cx1, cy1, cx2, cy2) = curve_segment(v, k_idx).unwrap_or((
time,
values[k_idx],
time2,
values2[k_idx],
));
let samples = compute_bezier_samples(
time,
values[k_idx],
cx1,
cy1,
cx2,
cy2,
time2,
values2[k_idx],
);
set_bezier_sample(
&mut curves,
frame_count,
frame,
k_idx,
bezier_seg_idx,
samples,
);
}
bezier_seg_idx += 6;
}
}
time = time2;
values = values2;
}
Ok(CurveFrames { frames, curves })
}
fn read_path_mix_timeline_json(keys: &[Value]) -> Result<CurveFrames, JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 3;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count * 4);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
if frame_count == 0 {
return Ok(CurveFrames { frames, curves });
}
curves[frame_count - 1] = CURVE_STEPPED;
let parse = |k: &Value| {
let mix_rotate = get_f32(k, "mixRotate", 1.0);
let mix_x = get_f32(k, "mixX", 1.0);
let mix_y = get_f32(k, "mixY", mix_x);
[mix_rotate, mix_x, mix_y]
};
let mut time = get_f32(&keys[0], "time", 0.0);
let mut values = parse(&keys[0]);
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
frames.extend_from_slice(&values);
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let values2 = parse(next);
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
for k_idx in 0..3 {
let (cx1, cy1, cx2, cy2) = curve_segment(v, k_idx).unwrap_or((
time,
values[k_idx],
time2,
values2[k_idx],
));
let samples = compute_bezier_samples(
time,
values[k_idx],
cx1,
cy1,
cx2,
cy2,
time2,
values2[k_idx],
);
set_bezier_sample(
&mut curves,
frame_count,
frame,
k_idx,
bezier_seg_idx,
samples,
);
}
bezier_seg_idx += 3;
}
}
time = time2;
values = values2;
}
Ok(CurveFrames { frames, curves })
}
fn read_deform_timeline_json(
keys: &[Value],
deform_length: usize,
scale: f32,
weighted: bool,
setup_vertices: &[f32],
) -> Result<(Vec<f32>, Vec<f32>, Vec<Vec<f32>>), JsonError> {
let frame_count = keys.len();
let mut bezier_count = 0usize;
for k in keys.iter().take(frame_count.saturating_sub(1)) {
if let Some(curve) = k.get("curve") {
if !curve.is_string() {
bezier_count += 1;
}
}
}
let mut frames: Vec<f32> = Vec::with_capacity(frame_count);
let mut curves: Vec<f32> = vec![0.0; frame_count + bezier_count * BEZIER_SIZE];
let mut vertices: Vec<Vec<f32>> = Vec::with_capacity(frame_count);
if frame_count == 0 {
return Ok((frames, curves, vertices));
}
curves[frame_count - 1] = CURVE_STEPPED;
let deform_of = |k: &Value| -> Vec<f32> {
match k.get("vertices").and_then(Value::as_array) {
None => {
if weighted {
vec![0.0; deform_length]
} else {
setup_vertices.to_vec()
}
}
Some(verts) => {
let start = get_int(k, "offset", 0) as usize;
let mut deform = vec![0.0_f32; deform_length];
for (i, v) in verts.iter().enumerate() {
let idx = start + i;
if idx >= deform_length {
break;
}
deform[idx] = v.as_f64().unwrap_or(0.0) as f32 * scale;
}
if !weighted {
for (d, s) in deform.iter_mut().zip(setup_vertices.iter()) {
*d += *s;
}
}
deform
}
}
};
let mut time = get_f32(&keys[0], "time", 0.0);
let mut bezier_seg_idx = 0usize;
for frame in 0..frame_count {
frames.push(time);
vertices.push(deform_of(&keys[frame]));
if frame + 1 >= frame_count {
break;
}
let next = &keys[frame + 1];
let time2 = get_f32(next, "time", 0.0);
let curve = keys[frame].get("curve");
match curve {
None => set_linear(&mut curves, frame),
Some(v) if v.as_str() == Some("stepped") => set_stepped(&mut curves, frame),
Some(v) => {
let (cx1, cy1, cx2, cy2) = curve_segment(v, 0).unwrap_or((time, 0.0, time2, 1.0));
let samples = compute_bezier_samples(time, 0.0, cx1, cy1, cx2, cy2, time2, 1.0);
let tail_offset = frame_count + bezier_seg_idx * BEZIER_SIZE;
curves[frame] = CURVE_BEZIER + tail_offset as f32;
curves[tail_offset..tail_offset + BEZIER_SIZE].copy_from_slice(&samples);
bezier_seg_idx += 1;
}
}
time = time2;
}
Ok((frames, curves, vertices))
}
fn deform_context(sd: &SkeletonData, att: AttachmentId) -> (bool, Vec<f32>) {
let Some(attachment) = sd.attachments.get(att.index()) else {
return (false, Vec::new());
};
let vd: &VertexData = match attachment {
Attachment::Mesh(m) => &m.vertex_data,
Attachment::BoundingBox(b) => &b.vertex_data,
Attachment::Path(p) => &p.vertex_data,
Attachment::Clipping(c) => &c.vertex_data,
_ => return (false, Vec::new()),
};
if vd.bones.is_empty() {
(false, vd.vertices.clone())
} else {
(true, Vec::new())
}
}
fn deform_frame_len(sd: &SkeletonData, att: AttachmentId) -> usize {
let Some(attachment) = sd.attachments.get(att.index()) else {
return 0;
};
let vd: &VertexData = match attachment {
Attachment::Mesh(m) => &m.vertex_data,
Attachment::BoundingBox(b) => &b.vertex_data,
Attachment::Path(p) => &p.vertex_data,
Attachment::Clipping(c) => &c.vertex_data,
_ => return 0,
};
if vd.bones.is_empty() {
vd.vertices.len()
} else {
vd.vertices.len() / 3 * 2
}
}