mocari 0.2.3

A pure Rust Live2D/Cubism-compatible runtime
Documentation
use serde::Deserialize;

use crate::{Error, Result};

const FORMAT: &str = "pose3.json";
pub const DEFAULT_POSE_FADE_IN_TIME: f32 = 0.5;

#[derive(Debug, Clone, PartialEq)]
pub struct Pose3 {
    kind: String,
    fade_in_time: Option<f32>,
    groups: Vec<Vec<PosePart>>,
}

impl Pose3 {
    pub fn from_json_str(source: &str) -> Result<Self> {
        let raw: RawPose3 = serde_json::from_str(source).map_err(|error| Error::InvalidJson {
            format: FORMAT,
            message: error.to_string(),
        })?;

        Ok(Self {
            kind: raw.kind,
            fade_in_time: raw.fade_in_time,
            groups: raw.groups,
        })
    }

    pub fn kind(&self) -> &str {
        &self.kind
    }

    pub fn fade_in_time(&self) -> Option<f32> {
        self.fade_in_time
    }

    pub fn resolved_fade_in_time(&self) -> f32 {
        resolved_pose_fade_in_time(self.fade_in_time)
    }

    pub fn groups(&self) -> &[Vec<PosePart>] {
        &self.groups
    }
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
struct RawPose3 {
    #[serde(rename = "Type")]
    kind: String,
    #[serde(rename = "FadeInTime", default)]
    fade_in_time: Option<f32>,
    #[serde(rename = "Groups", default)]
    groups: Vec<Vec<PosePart>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct PosePart {
    #[serde(rename = "Id")]
    id: String,
    #[serde(rename = "Link", default)]
    links: Vec<String>,
}

impl PosePart {
    pub fn id(&self) -> &str {
        &self.id
    }

    pub fn links(&self) -> &[String] {
        &self.links
    }
}

pub fn resolved_pose_fade_in_time(fade_in_time: Option<f32>) -> f32 {
    match fade_in_time {
        Some(value) if value >= 0.0 => value,
        _ => DEFAULT_POSE_FADE_IN_TIME,
    }
}

pub fn update_pose_group_opacities(
    parameter_values: &[f32],
    part_opacities: &mut [f32],
    delta_time_seconds: f32,
    fade_time_seconds: f32,
) -> Option<()> {
    if parameter_values.is_empty() || parameter_values.len() != part_opacities.len() {
        return None;
    }

    let delta_time_seconds = delta_time_seconds.max(0.0);
    let mut visible_part_index = None;
    let mut new_opacity = 1.0;

    for (index, parameter_value) in parameter_values.iter().enumerate() {
        if *parameter_value <= 0.001 {
            continue;
        }

        if visible_part_index.is_some() {
            break;
        }

        visible_part_index = Some(index);
        if fade_time_seconds == 0.0 {
            new_opacity = 1.0;
            continue;
        }

        new_opacity = (part_opacities[index] + (delta_time_seconds / fade_time_seconds)).min(1.0);
    }

    let visible_part_index = visible_part_index.unwrap_or(0);

    for (index, opacity) in part_opacities.iter_mut().enumerate() {
        if index == visible_part_index {
            *opacity = new_opacity;
            continue;
        }

        let mut target_opacity = if new_opacity < 0.5 {
            new_opacity * (0.5 - 1.0) / 0.5 + 1.0
        } else {
            (1.0 - new_opacity) * 0.5 / (1.0 - 0.5)
        };

        let back_opacity = (1.0 - target_opacity) * (1.0 - new_opacity);
        if back_opacity > 0.15 {
            target_opacity = 1.0 - 0.15 / (1.0 - new_opacity);
        }

        if *opacity > target_opacity {
            *opacity = target_opacity;
        }
    }

    Some(())
}

pub fn copy_pose_link_opacities(
    part_opacities: &mut [f32],
    source_index: usize,
    link_indices: &[usize],
) -> Option<()> {
    if source_index >= part_opacities.len()
        || link_indices
            .iter()
            .any(|link_index| *link_index >= part_opacities.len())
    {
        return None;
    }

    let opacity = part_opacities[source_index];
    for link_index in link_indices {
        part_opacities[*link_index] = opacity;
    }

    Some(())
}