- Add `gl.rs` for OpenGL ES 2.0 and EGL context management. - Introduce `mod.rs` to organize Live2D plugin modules. - Create `model.rs` for loading and managing Cubism models, including parameter handling and drawable data extraction. - Implement shader compilation and rendering context setup for Live2D models.
156 lines
4.4 KiB
Rust
156 lines
4.4 KiB
Rust
//! 资源管理: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<String>,
|
||
}
|
||
|
||
/// model3.json 文件引用
|
||
#[derive(Deserialize, Clone, Debug)]
|
||
pub struct FileReferences {
|
||
#[serde(rename = "Moc")]
|
||
pub moc: String,
|
||
#[serde(rename = "Textures", default)]
|
||
pub textures: Vec<String>,
|
||
#[serde(rename = "Physics", default)]
|
||
pub physics: Option<String>,
|
||
#[serde(rename = "Pose", default)]
|
||
pub pose: Option<String>,
|
||
#[serde(rename = "DisplayInfo", default)]
|
||
pub display_info: Option<String>,
|
||
}
|
||
|
||
/// 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<ModelGroup>,
|
||
}
|
||
|
||
impl Model3Json {
|
||
/// 从文件解析
|
||
pub fn from_path(path: &Path) -> Result<Self> {
|
||
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<String> {
|
||
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<String> {
|
||
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<u8>,
|
||
}
|
||
|
||
/// 加载 PNG 纹理(使用 image crate)
|
||
pub fn load_texture(path: &Path) -> Result<TextureImage> {
|
||
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<Vec<TextureImage>> {
|
||
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<PathBuf>,
|
||
}
|
||
|
||
/// 从 model3.json 相对路径(如 "haru/Haru.model3.json")解析资源路径
|
||
pub fn resolve_asset_paths(
|
||
live2d_base_dir: &Path,
|
||
model3_json_rel: &str,
|
||
) -> Result<ModelAssetPaths> {
|
||
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,
|
||
})
|
||
}
|