mocari 0.4.0

A pure Rust Live2D/Cubism-compatible runtime
Documentation
use mocari::core::{
    DeformerTransform, Vector2, WarpInterpolation, rotation_deformer_transform_point,
    transform_art_mesh_vertices_by_deformers, warp_deformer_transform_inside,
    warp_deformer_transform_target,
};

fn assert_vec_close(actual: Vector2, expected: Vector2) {
    assert!(
        (actual.x() - expected.x()).abs() < 0.00001,
        "expected x {}, got {}",
        expected.x(),
        actual.x()
    );
    assert!(
        (actual.y() - expected.y()).abs() < 0.00001,
        "expected y {}, got {}",
        expected.y(),
        actual.y()
    );
}

#[test]
fn rotation_deformer_applies_confirmed_forward_matrix() {
    let point = Vector2::new(1.0, 2.0);

    let transformed =
        rotation_deformer_transform_point(point, 90.0, 2.0, Vector2::new(10.0, 20.0), false, false);

    assert_vec_close(transformed, Vector2::new(6.0, 22.0));
}

#[test]
fn rotation_deformer_applies_flip_signs_to_axes() {
    let point = Vector2::new(1.0, 2.0);

    let transformed =
        rotation_deformer_transform_point(point, 90.0, 2.0, Vector2::new(10.0, 20.0), true, true);

    assert_vec_close(transformed, Vector2::new(14.0, 18.0));
}

#[test]
fn warp_deformer_inside_quad_uses_bilinear_grid_interpolation() {
    let grid = [
        Vector2::new(0.0, 0.0),
        Vector2::new(10.0, 0.0),
        Vector2::new(0.0, 20.0),
        Vector2::new(20.0, 20.0),
    ];

    let transformed = warp_deformer_transform_inside(
        Vector2::new(0.75, 0.75),
        &grid,
        1,
        1,
        WarpInterpolation::Quad,
    )
    .unwrap();

    assert_vec_close(transformed, Vector2::new(13.125, 15.0));
}

#[test]
fn warp_deformer_inside_triangle_uses_cell_diagonal() {
    let grid = [
        Vector2::new(0.0, 0.0),
        Vector2::new(10.0, 0.0),
        Vector2::new(0.0, 20.0),
        Vector2::new(20.0, 20.0),
    ];

    let lower = warp_deformer_transform_inside(
        Vector2::new(0.25, 0.5),
        &grid,
        1,
        1,
        WarpInterpolation::Triangle,
    )
    .unwrap();
    let upper = warp_deformer_transform_inside(
        Vector2::new(0.75, 0.75),
        &grid,
        1,
        1,
        WarpInterpolation::Triangle,
    )
    .unwrap();

    assert_vec_close(lower, Vector2::new(2.5, 10.0));
    assert_vec_close(upper, Vector2::new(12.5, 15.0));
}

#[test]
fn warp_deformer_inside_rejects_outside_or_incomplete_grid() {
    let grid = [
        Vector2::new(0.0, 0.0),
        Vector2::new(1.0, 0.0),
        Vector2::new(0.0, 1.0),
        Vector2::new(1.0, 1.0),
    ];

    assert!(
        warp_deformer_transform_inside(
            Vector2::new(1.0, 0.5),
            &grid,
            1,
            1,
            WarpInterpolation::Quad
        )
        .is_none()
    );
    assert!(
        warp_deformer_transform_inside(
            Vector2::new(0.5, 0.5),
            &grid[..3],
            1,
            1,
            WarpInterpolation::Quad
        )
        .is_none()
    );
}

#[test]
fn warp_deformer_target_extrapolates_points_outside_unit_grid() {
    let grid = [
        Vector2::new(0.0, 0.0),
        Vector2::new(10.0, 0.0),
        Vector2::new(0.0, 20.0),
        Vector2::new(10.0, 20.0),
    ];

    let transformed = warp_deformer_transform_target(
        Vector2::new(1.25, 0.5),
        &grid,
        1,
        1,
        WarpInterpolation::Quad,
    )
    .unwrap();

    assert_vec_close(transformed, Vector2::new(12.5, 10.0));
}

#[test]
fn warp_deformer_target_extrapolates_non_affine_grid_via_corner_basis() {
    // c11 deviates from the affine corner (10, 10), so edge-cell linear
    // extrapolation and the official corner-basis extrapolation diverge.
    let grid = [
        Vector2::new(0.0, 0.0),
        Vector2::new(10.0, 0.0),
        Vector2::new(0.0, 10.0),
        Vector2::new(20.0, 30.0),
    ];

    let transformed = warp_deformer_transform_target(
        Vector2::new(1.25, 0.5),
        &grid,
        1,
        1,
        WarpInterpolation::Quad,
    )
    .unwrap();

    assert_vec_close(transformed, Vector2::new(19.0625, 18.125));
}

#[test]
fn applies_deformer_path_to_art_mesh_vertices() {
    let grid = [
        Vector2::new(10.0, 20.0),
        Vector2::new(12.0, 20.0),
        Vector2::new(10.0, 22.0),
        Vector2::new(12.0, 22.0),
    ];
    let vertices = [Vector2::new(0.25, 0.0), Vector2::new(0.0, -0.25)];
    let transforms = [
        DeformerTransform::Rotation {
            angle_degrees: 90.0,
            scale: 1.0,
            translation: Vector2::new(0.25, 0.25),
            flip_x: false,
            flip_y: false,
        },
        DeformerTransform::Warp {
            grid: &grid,
            cols: 1,
            rows: 1,
            interpolation: WarpInterpolation::Quad,
        },
    ];

    let out = transform_art_mesh_vertices_by_deformers(&vertices, &transforms).unwrap();

    assert_vec_close(out[0], Vector2::new(10.5, 21.0));
    assert_vec_close(out[1], Vector2::new(11.0, 20.5));
}