spine2d 0.3.0

Pure Rust runtime for Spine 4.3 (unofficial)
Documentation
use crate::{MixBlend, Skeleton, SkeletonData, apply_animation};

const SKELETON_WITH_PATH_CONSTRAINT_AND_TIMELINES: &str = r#"
{
  "skeleton": { "spine": "4.3.00" },
  "bones": [
    { "name": "root" },
    { "name": "b", "parent": "root" }
  ],
  "slots": [
    { "name": "pathSlot", "bone": "root", "attachment": "p" }
  ],
  "skins": [
    {
      "name": "default",
      "attachments": {
        "pathSlot": {
          "p": {
            "type": "path",
            "vertexCount": 3,
            "vertices": [ 0, 0, 10, 0, 20, 0 ],
            "lengths": [ 20 ],
            "closed": false,
            "constantSpeed": true
          }
        }
      }
    }
  ],
  "path": [
    {
      "name": "pc",
      "bones": ["b"],
      "target": "pathSlot",
      "positionMode": "percent",
      "spacingMode": "length",
      "rotateMode": "tangent",
      "position": 0,
      "spacing": 0,
      "mixRotate": 0,
      "mixX": 0,
      "mixY": 0
    }
  ],
  "animations": {
    "anim": {
      "path": {
        "pc": {
          "mix": [
            { "time": 0.0, "mixRotate": 0.0, "mixX": 0.0, "mixY": 0.0 },
            { "time": 1.0, "mixRotate": 1.0, "mixX": 1.0, "mixY": 1.0 }
          ],
          "position": [
            { "time": 0.0, "value": 0.0 },
            { "time": 1.0, "value": 10.0 }
          ],
          "spacing": [
            { "time": 0.0, "value": 0.0 },
            { "time": 1.0, "value": 5.0 }
          ]
        }
      }
    }
  }
}
"#;

fn assert_approx(actual: f32, expected: f32) {
    let diff = (actual - expected).abs();
    assert!(
        diff <= 1.0e-3,
        "expected {expected}, got {actual} (diff {diff})"
    );
}

#[test]
fn path_constraint_timelines_update_runtime_values() {
    let data = SkeletonData::from_json_str(SKELETON_WITH_PATH_CONSTRAINT_AND_TIMELINES).unwrap();
    let (_, anim) = data.animation("anim").unwrap();
    let mut skeleton = Skeleton::new(data.clone());

    skeleton.set_to_setup_pose();
    apply_animation(anim, &mut skeleton, 0.5, false, 1.0, MixBlend::Replace);

    assert_approx(skeleton.path_constraints[0].mix_rotate, 0.5);
    assert_approx(skeleton.path_constraints[0].mix_x, 0.5);
    assert_approx(skeleton.path_constraints[0].mix_y, 0.5);
    assert_approx(skeleton.path_constraints[0].position, 5.0);
    assert_approx(skeleton.path_constraints[0].spacing, 2.5);
}