mocari 0.1.0

A pure Rust Live2D/Cubism-compatible runtime, with an optional wgpu renderer
Documentation
use std::collections::BTreeMap;

use serde::Deserialize;

use crate::{Error, Result};

const FORMAT: &str = "model3.json";
const SUPPORTED_VERSION: u32 = 3;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Model3 {
    version: u32,
    file_references: FileReferences,
    groups: Vec<Group>,
    hit_areas: Vec<HitArea>,
}

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

        if raw.version != SUPPORTED_VERSION {
            return Err(Error::UnsupportedVersion {
                format: FORMAT,
                version: raw.version,
            });
        }

        Ok(Self {
            version: raw.version,
            file_references: raw.file_references,
            groups: raw.groups,
            hit_areas: raw.hit_areas,
        })
    }

    pub fn version(&self) -> u32 {
        self.version
    }

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

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

    pub fn physics(&self) -> Option<&str> {
        self.file_references.physics.as_deref()
    }

    pub fn pose(&self) -> Option<&str> {
        self.file_references.pose.as_deref()
    }

    pub fn display_info(&self) -> Option<&str> {
        self.file_references.display_info.as_deref()
    }

    pub fn motions(&self) -> &BTreeMap<String, Vec<MotionReference>> {
        &self.file_references.motions
    }

    pub fn groups(&self) -> &[Group] {
        &self.groups
    }

    pub fn hit_areas(&self) -> &[HitArea] {
        &self.hit_areas
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct RawModel3 {
    #[serde(rename = "Version")]
    version: u32,
    #[serde(rename = "FileReferences")]
    file_references: FileReferences,
    #[serde(rename = "Groups", default)]
    groups: Vec<Group>,
    #[serde(rename = "HitAreas", default)]
    hit_areas: Vec<HitArea>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct FileReferences {
    #[serde(rename = "Moc")]
    moc: String,
    #[serde(rename = "Textures")]
    textures: Vec<String>,
    #[serde(rename = "Physics", default)]
    physics: Option<String>,
    #[serde(rename = "Pose", default)]
    pose: Option<String>,
    #[serde(rename = "DisplayInfo", default)]
    display_info: Option<String>,
    #[serde(rename = "Motions", default)]
    motions: BTreeMap<String, Vec<MotionReference>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct MotionReference {
    #[serde(rename = "File")]
    file: String,
}

impl MotionReference {
    pub fn file(&self) -> &str {
        &self.file
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Group {
    #[serde(rename = "Target")]
    target: String,
    #[serde(rename = "Name")]
    name: String,
    #[serde(rename = "Ids")]
    ids: Vec<String>,
}

impl Group {
    pub fn target(&self) -> &str {
        &self.target
    }

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

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

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct HitArea {
    #[serde(rename = "Id")]
    id: String,
    #[serde(rename = "Name")]
    name: String,
}

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

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