feat(live2d): implement OpenGL ES 2.0 rendering backend with EGL support
- 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.
This commit is contained in:
155
src/plugins/live2d/assets.rs
Normal file
155
src/plugins/live2d/assets.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
//! 资源管理: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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user