//! 资源管理:model3.json 解析 + PNG 纹理加载 use anyhow::{anyhow, Context, Result}; use serde::Deserialize; use std::path::{Path, PathBuf}; /// model3.json 中的 Groups 条目 #[derive(Deserialize, Clone, Debug)] pub struct ModelGroup { #[serde(rename = "Target")] pub target: String, #[serde(rename = "Name")] pub name: String, #[serde(rename = "Ids")] pub ids: Vec, } /// model3.json 文件引用 #[derive(Deserialize, Clone, Debug)] pub struct FileReferences { #[serde(rename = "Moc")] pub moc: String, #[serde(rename = "Textures", default)] pub textures: Vec, #[serde(rename = "Physics", default)] pub physics: Option, #[serde(rename = "Pose", default)] pub pose: Option, #[serde(rename = "DisplayInfo", default)] pub display_info: Option, } /// model3.json 根结构 #[derive(Deserialize, Clone, Debug)] pub struct Model3Json { #[serde(rename = "Version")] pub version: i32, #[serde(rename = "FileReferences")] pub file_references: FileReferences, #[serde(rename = "Groups", default)] pub groups: Vec, } impl Model3Json { /// 从文件解析 pub fn from_path(path: &Path) -> Result { let text = std::fs::read_to_string(path) .with_context(|| format!("读取 model3.json 失败: {}", path.display()))?; let json: Model3Json = serde_json::from_str(&text) .with_context(|| format!("解析 model3.json 失败: {}", path.display()))?; Ok(json) } /// 获取 LipSync 参数 ID 列表 pub fn lip_sync_ids(&self) -> Vec { self.groups .iter() .find(|g| g.target == "Parameter" && g.name == "LipSync") .map(|g| g.ids.clone()) .unwrap_or_else(|| vec!["ParamMouthOpenY".to_string()]) } /// 获取 EyeBlink 参数 ID 列表 pub fn eye_blink_ids(&self) -> Vec { self.groups .iter() .find(|g| g.target == "Parameter" && g.name == "EyeBlink") .map(|g| g.ids.clone()) .unwrap_or_else(|| { vec!["ParamEyeLOpen".to_string(), "ParamEyeROpen".to_string()] }) } } /// 加载的纹理图像(RGBA) pub struct TextureImage { pub width: u32, pub height: u32, pub rgba: Vec, } /// 加载 PNG 纹理(使用 image crate) pub fn load_texture(path: &Path) -> Result { let img = image::open(path) .with_context(|| format!("加载纹理失败: {}", path.display()))? .to_rgba8(); let width = img.width(); let height = img.height(); let rgba = img.into_raw(); Ok(TextureImage { width, height, rgba, }) } /// 加载 model3.json 引用的所有纹理(返回按 texture_00, texture_01 顺序的列表) pub fn load_all_textures( model3_dir: &Path, file_refs: &FileReferences, ) -> Result> { let mut textures = Vec::new(); for tex_rel in &file_refs.textures { let tex_path = model3_dir.join(tex_rel); let img = load_texture(&tex_path)?; println!( "[Live2D] 纹理加载: {} ({}x{})", tex_rel, img.width, img.height ); textures.push(img); } Ok(textures) } /// 解析 model3.json 并返回完整路径集 pub struct ModelAssetPaths { pub model3_json: PathBuf, pub model3_dir: PathBuf, pub moc3: PathBuf, pub textures: Vec, } /// 从 model3.json 相对路径(如 "haru/Haru.model3.json")解析资源路径 pub fn resolve_asset_paths( live2d_base_dir: &Path, model3_json_rel: &str, ) -> Result { let model3_json = live2d_base_dir.join(model3_json_rel); if !model3_json.exists() { return Err(anyhow!( "model3.json 不存在: {}", model3_json.display() )); } let model3_dir = model3_json .parent() .ok_or_else(|| anyhow!("无法获取 model3.json 的父目录"))? .to_path_buf(); let model3 = Model3Json::from_path(&model3_json)?; let moc3 = model3_dir.join(&model3.file_references.moc); let textures = model3 .file_references .textures .iter() .map(|t| model3_dir.join(t)) .collect(); Ok(ModelAssetPaths { model3_json, model3_dir, moc3, textures, }) }