use super::gltf::{
Bg3BoneBinding, Bg3MeshProfile, Bg3SkeletonProfile, Bg3TopologyGroup, Bg3Transform, GltfBuilder,
};
use super::gr2_reader::{Gr2Reader, MeshData, MeshExtendedData, Model, Skeleton};
use crate::error::{Error, Result};
use std::path::Path;
fn bool_flag(v: bool) -> Option<bool> {
if v { Some(true) } else { None }
}
pub(super) fn remap_mesh_bone_indices(mesh: &mut MeshData, skeleton: &Skeleton, dfs_remap: &[u8]) {
let binding_to_joint: Vec<u8> = mesh
.bone_bindings
.iter()
.map(|bb| {
let skel_idx = skeleton
.bones
.iter()
.position(|b| b.name == bb.bone_name)
.unwrap_or(0);
dfs_remap[skel_idx]
})
.collect();
for vertex in &mut mesh.vertices {
for i in 0..4 {
let binding_idx = vertex.bone_indices[i] as usize;
vertex.bone_indices[i] = if binding_idx < binding_to_joint.len() {
binding_to_joint[binding_idx]
} else {
0
};
}
}
}
fn to_bg3_profile(ext: &MeshExtendedData, mesh: &MeshData, idx: usize) -> Bg3MeshProfile {
let (proxy_geometry, cloth_physics, cloth_01, cloth_02, cloth_04, impostor, lod_distance) =
if let Some(ref props) = ext.mesh_properties {
(
bool_flag(props.model_flags.has_proxy_geometry),
bool_flag(props.cloth_flags.cloth_physics),
bool_flag(props.cloth_flags.cloth_01),
bool_flag(props.cloth_flags.cloth_02),
bool_flag(props.cloth_flags.cloth_04),
bool_flag(props.is_impostor),
if props.lod_distance < f32::MAX {
Some(props.lod_distance)
} else {
None
},
)
} else {
(None, None, None, None, None, None, None)
};
let bone_bindings = if mesh.bone_bindings.is_empty() {
None
} else {
Some(
mesh.bone_bindings
.iter()
.map(|bb| Bg3BoneBinding {
bone_name: bb.bone_name.clone(),
obb_min: bb.obb_min,
obb_max: bb.obb_max,
tri_count: bb.tri_count,
tri_indices: bb.tri_indices.clone(),
})
.collect(),
)
};
let material_bindings = if mesh.material_binding_names.is_empty() {
None
} else {
Some(mesh.material_binding_names.clone())
};
let topology_groups = if mesh.topology_groups.is_empty() {
None
} else {
Some(
mesh.topology_groups
.iter()
.map(|tg| Bg3TopologyGroup {
material_index: tg.material_index,
tri_first: tg.tri_first,
tri_count: tg.tri_count,
})
.collect(),
)
};
let user_defined_properties = ext.user_defined_properties.clone();
Bg3MeshProfile {
rigid: bool_flag(ext.rigid != 0),
cloth: bool_flag(ext.cloth != 0),
mesh_proxy: bool_flag(ext.mesh_proxy != 0),
spring: bool_flag(ext.spring != 0),
occluder: bool_flag(ext.occluder != 0),
lod: if ext.lod != 0 { Some(ext.lod) } else { None },
export_order: Some(idx as i32),
proxy_geometry,
cloth_physics,
cloth_01,
cloth_02,
cloth_04,
impostor,
lod_distance,
parent_bone: None,
bone_bindings,
material_bindings,
topology_groups,
user_defined_properties,
}
}
pub(super) fn to_bg3_skeleton_profile_from(
skel: &Skeleton,
models: &[Model],
) -> Bg3SkeletonProfile {
let model = models.first();
Bg3SkeletonProfile {
lod_type: Some(skel.lod_type),
bone_lod_error: Some(skel.bones.iter().map(|b| b.lod_error).collect()),
model_name: model.map(|m| m.name.clone()),
model_mesh_bindings: model.map(|m| m.mesh_binding_names.clone()),
initial_placement: model.map(|m| Bg3Transform {
translation: m.initial_placement.translation,
rotation: m.initial_placement.rotation,
scale_shear: m.initial_placement.scale_shear,
}),
}
}
pub fn convert_gr2_to_gltf(input_path: &Path, output_path: &Path) -> Result<()> {
convert_gr2_to_gltf_with_progress(input_path, output_path, &|_| {})
}
pub fn convert_gr2_to_gltf_with_progress(
input_path: &Path,
output_path: &Path,
progress: crate::converter::gr2_gltf::Gr2ProgressCallback,
) -> Result<()> {
use crate::converter::gr2_gltf::{Gr2Phase, Gr2Progress};
progress(&Gr2Progress::with_file(
Gr2Phase::ReadingFile,
1,
5,
input_path.display().to_string(),
));
let file_data = std::fs::read(input_path)?;
let reader = Gr2Reader::new(&file_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingSkeleton, 2, 5));
let skeleton = reader.parse_skeleton(&file_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingMeshes, 3, 5));
let meshes = reader.parse_meshes(&file_data)?;
if meshes.is_empty() {
let info = reader.get_content_info(&file_data)?;
return Err(Error::ConversionError(format!(
"No meshes found in GR2 file (contains: {})",
info.describe()
)));
}
progress(&Gr2Progress::with_file(
Gr2Phase::BuildingDocument,
4,
5,
format!("{} meshes", meshes.len()),
));
let mut builder = GltfBuilder::new();
let (skin_idx, root_bone_idx, bone_remap) = if let Some(ref skel) = skeleton {
let models = reader.parse_models(&file_data).unwrap_or_default();
let skel_profile = to_bg3_skeleton_profile_from(skel, &models);
let result = builder.add_skeleton_with_profile(skel, skel_profile);
let root_idx = Some(builder.bone_node_offset);
(Some(result.skin_idx), root_idx, Some(result.bone_remap))
} else {
(None, None, None)
};
for (i, mesh) in meshes.iter().enumerate() {
let profile = mesh
.extended_data
.as_ref()
.map(|ext| to_bg3_profile(ext, mesh, i));
let mut mesh = mesh.clone();
if let (Some(remap), Some(skel)) = (&bone_remap, &skeleton) {
remap_mesh_bone_indices(&mut mesh, skel, remap);
}
builder.add_mesh_with_profile(&mesh, skin_idx, profile);
}
progress(&Gr2Progress::with_file(
Gr2Phase::WritingOutput,
5,
5,
output_path.display().to_string(),
));
builder.export_gltf(output_path, root_bone_idx)?;
progress(&Gr2Progress::new(Gr2Phase::Complete, 5, 5));
Ok(())
}
pub fn convert_gr2_to_glb(input_path: &Path, output_path: &Path) -> Result<()> {
convert_gr2_to_glb_with_progress(input_path, output_path, &|_| {})
}
pub fn convert_gr2_to_glb_with_progress(
input_path: &Path,
output_path: &Path,
progress: crate::converter::gr2_gltf::Gr2ProgressCallback,
) -> Result<()> {
use crate::converter::gr2_gltf::{Gr2Phase, Gr2Progress};
progress(&Gr2Progress::with_file(
Gr2Phase::ReadingFile,
1,
5,
input_path.display().to_string(),
));
let file_data = std::fs::read(input_path)?;
let reader = Gr2Reader::new(&file_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingSkeleton, 2, 5));
let skeleton = reader.parse_skeleton(&file_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingMeshes, 3, 5));
let meshes = reader.parse_meshes(&file_data)?;
if meshes.is_empty() {
let info = reader.get_content_info(&file_data)?;
return Err(Error::ConversionError(format!(
"No meshes found in GR2 file (contains: {})",
info.describe()
)));
}
progress(&Gr2Progress::with_file(
Gr2Phase::BuildingDocument,
4,
5,
format!("{} meshes", meshes.len()),
));
let mut builder = GltfBuilder::new();
let (skin_idx, root_bone_idx, bone_remap) = if let Some(ref skel) = skeleton {
let models = reader.parse_models(&file_data).unwrap_or_default();
let skel_profile = to_bg3_skeleton_profile_from(skel, &models);
let result = builder.add_skeleton_with_profile(skel, skel_profile);
let root_idx = Some(builder.bone_node_offset);
(Some(result.skin_idx), root_idx, Some(result.bone_remap))
} else {
(None, None, None)
};
for (i, mesh) in meshes.iter().enumerate() {
let profile = mesh
.extended_data
.as_ref()
.map(|ext| to_bg3_profile(ext, mesh, i));
let mut mesh = mesh.clone();
if let (Some(remap), Some(skel)) = (&bone_remap, &skeleton) {
remap_mesh_bone_indices(&mut mesh, skel, remap);
}
builder.add_mesh_with_profile(&mesh, skin_idx, profile);
}
progress(&Gr2Progress::with_file(
Gr2Phase::WritingOutput,
5,
5,
output_path.display().to_string(),
));
builder.export_glb(output_path, root_bone_idx)?;
progress(&Gr2Progress::new(Gr2Phase::Complete, 5, 5));
Ok(())
}
pub fn convert_gr2_bytes_to_glb(gr2_data: &[u8]) -> Result<Vec<u8>> {
convert_gr2_bytes_to_glb_with_progress(gr2_data, &|_| {})
}
pub fn convert_gr2_bytes_to_glb_with_progress(
gr2_data: &[u8],
progress: crate::converter::gr2_gltf::Gr2ProgressCallback,
) -> Result<Vec<u8>> {
use crate::converter::gr2_gltf::{Gr2Phase, Gr2Progress};
progress(&Gr2Progress::new(Gr2Phase::ReadingFile, 1, 5));
let reader = Gr2Reader::new(gr2_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingSkeleton, 2, 5));
let skeleton = reader.parse_skeleton(gr2_data)?;
progress(&Gr2Progress::new(Gr2Phase::ParsingMeshes, 3, 5));
let meshes = reader.parse_meshes(gr2_data)?;
if meshes.is_empty() {
let info = reader.get_content_info(gr2_data)?;
return Err(Error::ConversionError(format!(
"No meshes found in GR2 file (contains: {})",
info.describe()
)));
}
progress(&Gr2Progress::with_file(
Gr2Phase::BuildingDocument,
4,
5,
format!("{} meshes", meshes.len()),
));
let mut builder = GltfBuilder::new();
let (skin_idx, root_bone_idx, bone_remap) = if let Some(ref skel) = skeleton {
let models = reader.parse_models(gr2_data).unwrap_or_default();
let skel_profile = to_bg3_skeleton_profile_from(skel, &models);
let result = builder.add_skeleton_with_profile(skel, skel_profile);
let root_idx = Some(builder.bone_node_offset);
(Some(result.skin_idx), root_idx, Some(result.bone_remap))
} else {
(None, None, None)
};
for (i, mesh) in meshes.iter().enumerate() {
let profile = mesh
.extended_data
.as_ref()
.map(|ext| to_bg3_profile(ext, mesh, i));
let mut mesh = mesh.clone();
if let (Some(remap), Some(skel)) = (&bone_remap, &skeleton) {
remap_mesh_bone_indices(&mut mesh, skel, remap);
}
builder.add_mesh_with_profile(&mesh, skin_idx, profile);
}
progress(&Gr2Progress::new(Gr2Phase::WritingOutput, 5, 5));
let result = builder.build_glb(root_bone_idx)?;
progress(&Gr2Progress::new(Gr2Phase::Complete, 5, 5));
Ok(result)
}