use std::collections::HashMap;
use crate::error::{Result, WdlError};
use crate::types::*;
use crate::version::WdlVersion;
pub fn convert_wdl_file(file: &WdlFile, target_version: WdlVersion) -> Result<WdlFile> {
let mut new_file = WdlFile::with_version(target_version);
new_file.map_tile_offsets = file.map_tile_offsets;
new_file.heightmap_tiles = file.heightmap_tiles.clone();
if target_version.has_maho_chunk() {
if file.version.has_maho_chunk() {
new_file.holes_data = file.holes_data.clone();
} else {
for &key in file.heightmap_tiles.keys() {
new_file.holes_data.insert(key, HolesData::new());
}
}
} else if file.version.has_maho_chunk() && !file.holes_data.is_empty() {
return Err(WdlError::VersionConversionError(
"Cannot convert from a version with holes data to one without holes support"
.to_string(),
));
}
convert_model_data(file, &mut new_file)?;
rebuild_chunks(&mut new_file)?;
Ok(new_file)
}
fn convert_model_data(source: &WdlFile, target: &mut WdlFile) -> Result<()> {
if target.version.has_wmo_chunks() {
if source.version.has_wmo_chunks() {
target.wmo_filenames = source.wmo_filenames.clone();
target.wmo_indices = source.wmo_indices.clone();
target.wmo_placements = source.wmo_placements.clone();
} else if source.version.has_ml_chunks() {
convert_ml_to_wmo(source, target)?;
}
}
if target.version.has_ml_chunks() {
if source.version.has_ml_chunks() {
target.m2_placements = source.m2_placements.clone();
target.m2_visibility = source.m2_visibility.clone();
target.wmo_legion_placements = source.wmo_legion_placements.clone();
target.wmo_legion_visibility = source.wmo_legion_visibility.clone();
} else if source.version.has_wmo_chunks() {
convert_wmo_to_ml(source, target)?;
}
}
Ok(())
}
fn convert_wmo_to_ml(source: &WdlFile, target: &mut WdlFile) -> Result<()> {
for placement in &source.wmo_placements {
let m2_placement = M2Placement {
id: placement.id,
m2_id: placement.wmo_id, position: placement.position.clone(),
rotation: placement.rotation.clone(),
scale: 1.0, flags: placement.flags as u32,
};
let visibility_info = M2VisibilityInfo {
bounds: placement.bounds.clone(),
radius: calculate_radius(&placement.bounds),
};
target.wmo_legion_placements.push(m2_placement);
target.wmo_legion_visibility.push(visibility_info);
}
Ok(())
}
fn convert_ml_to_wmo(source: &WdlFile, target: &mut WdlFile) -> Result<()> {
let mut file_ids = HashMap::new();
let mut next_idx = 0;
for (i, placement) in source.wmo_legion_placements.iter().enumerate() {
let idx = match file_ids.get(&placement.m2_id) {
Some(&idx) => idx,
None => {
let idx = next_idx;
next_idx += 1;
target
.wmo_filenames
.push(format!("FileDataID_{}", placement.m2_id));
file_ids.insert(placement.m2_id, idx);
idx
}
};
if !target.wmo_indices.contains(&idx) {
target.wmo_indices.push(idx);
}
let visibility = if i < source.wmo_legion_visibility.len() {
&source.wmo_legion_visibility[i]
} else {
return Err(WdlError::VersionConversionError(
"Missing visibility info for WMO placement".to_string(),
));
};
let model_placement = ModelPlacement {
id: placement.id,
wmo_id: idx,
position: placement.position.clone(),
rotation: placement.rotation.clone(),
bounds: visibility.bounds.clone(),
flags: placement.flags as u16,
doodad_set: 0, name_set: 0, padding: 0,
};
target.wmo_placements.push(model_placement);
}
Ok(())
}
fn calculate_radius(bounds: &BoundingBox) -> f32 {
let dx = (bounds.max.x - bounds.min.x) * 0.5;
let dy = (bounds.max.y - bounds.min.y) * 0.5;
let dz = (bounds.max.z - bounds.min.z) * 0.5;
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn rebuild_chunks(file: &mut WdlFile) -> Result<()> {
file.chunks.clear();
let mut mver_data = Vec::new();
mver_data.extend_from_slice(&file.version_number.to_le_bytes());
file.chunks.push(Chunk::new(MVER_MAGIC, mver_data));
if file.version.has_wmo_chunks() && !file.wmo_filenames.is_empty() {
let mut mwmo_data = Vec::new();
for name in &file.wmo_filenames {
mwmo_data.extend_from_slice(name.as_bytes());
mwmo_data.push(0); }
file.chunks.push(Chunk::new(MWMO_MAGIC, mwmo_data));
let mut mwid_data = Vec::new();
for &idx in &file.wmo_indices {
mwid_data.extend_from_slice(&idx.to_le_bytes());
}
file.chunks.push(Chunk::new(MWID_MAGIC, mwid_data));
let mut modf_data = Vec::new();
for placement in &file.wmo_placements {
modf_data.extend_from_slice(&placement.id.to_le_bytes());
modf_data.extend_from_slice(&placement.wmo_id.to_le_bytes());
modf_data.extend_from_slice(&placement.position.x.to_le_bytes());
modf_data.extend_from_slice(&placement.position.y.to_le_bytes());
modf_data.extend_from_slice(&placement.position.z.to_le_bytes());
modf_data.extend_from_slice(&placement.rotation.x.to_le_bytes());
modf_data.extend_from_slice(&placement.rotation.y.to_le_bytes());
modf_data.extend_from_slice(&placement.rotation.z.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.min.x.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.min.y.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.min.z.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.max.x.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.max.y.to_le_bytes());
modf_data.extend_from_slice(&placement.bounds.max.z.to_le_bytes());
modf_data.extend_from_slice(&placement.flags.to_le_bytes());
modf_data.extend_from_slice(&placement.doodad_set.to_le_bytes());
modf_data.extend_from_slice(&placement.name_set.to_le_bytes());
modf_data.extend_from_slice(&placement.padding.to_le_bytes());
}
file.chunks.push(Chunk::new(MODF_MAGIC, modf_data));
}
if file.version.has_ml_chunks() {
if !file.m2_placements.is_empty() {
let mut mldd_data = Vec::new();
for placement in &file.m2_placements {
mldd_data.extend_from_slice(&placement.id.to_le_bytes());
mldd_data.extend_from_slice(&placement.m2_id.to_le_bytes());
mldd_data.extend_from_slice(&placement.position.x.to_le_bytes());
mldd_data.extend_from_slice(&placement.position.y.to_le_bytes());
mldd_data.extend_from_slice(&placement.position.z.to_le_bytes());
mldd_data.extend_from_slice(&placement.rotation.x.to_le_bytes());
mldd_data.extend_from_slice(&placement.rotation.y.to_le_bytes());
mldd_data.extend_from_slice(&placement.rotation.z.to_le_bytes());
mldd_data.extend_from_slice(&placement.scale.to_le_bytes());
mldd_data.extend_from_slice(&placement.flags.to_le_bytes());
}
file.chunks.push(Chunk::new(MLDD_MAGIC, mldd_data));
}
if !file.m2_visibility.is_empty() {
let mut mldx_data = Vec::new();
for info in &file.m2_visibility {
mldx_data.extend_from_slice(&info.bounds.min.x.to_le_bytes());
mldx_data.extend_from_slice(&info.bounds.min.y.to_le_bytes());
mldx_data.extend_from_slice(&info.bounds.min.z.to_le_bytes());
mldx_data.extend_from_slice(&info.bounds.max.x.to_le_bytes());
mldx_data.extend_from_slice(&info.bounds.max.y.to_le_bytes());
mldx_data.extend_from_slice(&info.bounds.max.z.to_le_bytes());
mldx_data.extend_from_slice(&info.radius.to_le_bytes());
}
file.chunks.push(Chunk::new(MLDX_MAGIC, mldx_data));
}
if !file.wmo_legion_placements.is_empty() {
let mut mlmd_data = Vec::new();
for placement in &file.wmo_legion_placements {
mlmd_data.extend_from_slice(&placement.id.to_le_bytes());
mlmd_data.extend_from_slice(&placement.m2_id.to_le_bytes());
mlmd_data.extend_from_slice(&placement.position.x.to_le_bytes());
mlmd_data.extend_from_slice(&placement.position.y.to_le_bytes());
mlmd_data.extend_from_slice(&placement.position.z.to_le_bytes());
mlmd_data.extend_from_slice(&placement.rotation.x.to_le_bytes());
mlmd_data.extend_from_slice(&placement.rotation.y.to_le_bytes());
mlmd_data.extend_from_slice(&placement.rotation.z.to_le_bytes());
mlmd_data.extend_from_slice(&placement.scale.to_le_bytes());
mlmd_data.extend_from_slice(&placement.flags.to_le_bytes());
}
file.chunks.push(Chunk::new(MLMD_MAGIC, mlmd_data));
}
if !file.wmo_legion_visibility.is_empty() {
let mut mlmx_data = Vec::new();
for info in &file.wmo_legion_visibility {
mlmx_data.extend_from_slice(&info.bounds.min.x.to_le_bytes());
mlmx_data.extend_from_slice(&info.bounds.min.y.to_le_bytes());
mlmx_data.extend_from_slice(&info.bounds.min.z.to_le_bytes());
mlmx_data.extend_from_slice(&info.bounds.max.x.to_le_bytes());
mlmx_data.extend_from_slice(&info.bounds.max.y.to_le_bytes());
mlmx_data.extend_from_slice(&info.bounds.max.z.to_le_bytes());
mlmx_data.extend_from_slice(&info.radius.to_le_bytes());
}
file.chunks.push(Chunk::new(MLMX_MAGIC, mlmx_data));
}
}
let mut maof_data = Vec::new();
for &offset in &file.map_tile_offsets {
maof_data.extend_from_slice(&offset.to_le_bytes());
}
file.chunks.push(Chunk::new(MAOF_MAGIC, maof_data));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{BoundingBox, ModelPlacement, Vec3d, WdlFile};
use crate::version::WdlVersion;
#[test]
fn test_calculate_radius() {
let bounds = BoundingBox {
min: Vec3d::new(-1.0, -1.0, -1.0),
max: Vec3d::new(1.0, 1.0, 1.0),
};
let radius = calculate_radius(&bounds);
let expected = (3.0f32).sqrt();
assert!((radius - expected).abs() < 0.001);
}
#[test]
fn test_convert_wotlk_to_legion() {
let mut wotlk_file = WdlFile::with_version(WdlVersion::Wotlk);
wotlk_file.wmo_filenames.push("test.wmo".to_string());
wotlk_file.wmo_indices.push(0);
let placement = ModelPlacement {
id: 1,
wmo_id: 0,
position: Vec3d::new(1.0, 2.0, 3.0),
rotation: Vec3d::new(0.0, 0.0, 0.0),
bounds: BoundingBox {
min: Vec3d::new(-1.0, -1.0, -1.0),
max: Vec3d::new(1.0, 1.0, 1.0),
},
flags: 0,
doodad_set: 0,
name_set: 0,
padding: 0,
};
wotlk_file.wmo_placements.push(placement);
let result = convert_wdl_file(&wotlk_file, WdlVersion::Legion);
assert!(result.is_ok());
let legion_file = result.unwrap();
assert_eq!(legion_file.version, WdlVersion::Legion);
assert!(legion_file.wmo_filenames.is_empty()); assert!(legion_file.wmo_indices.is_empty()); assert!(legion_file.wmo_placements.is_empty());
assert_eq!(legion_file.wmo_legion_placements.len(), 1);
assert_eq!(legion_file.wmo_legion_visibility.len(), 1);
let legion_placement = &legion_file.wmo_legion_placements[0];
assert_eq!(legion_placement.id, 1);
assert_eq!(legion_placement.position.x, 1.0);
assert_eq!(legion_placement.position.y, 2.0);
assert_eq!(legion_placement.position.z, 3.0);
}
#[test]
fn test_convert_legion_to_wotlk() {
let mut legion_file = WdlFile::with_version(WdlVersion::Legion);
let m2_placement = M2Placement {
id: 1,
m2_id: 1234, position: Vec3d::new(1.0, 2.0, 3.0),
rotation: Vec3d::new(0.0, 0.0, 0.0),
scale: 1.0,
flags: 0,
};
legion_file.m2_placements.push(m2_placement);
let visibility = M2VisibilityInfo {
bounds: BoundingBox {
min: Vec3d::new(-1.0, -1.0, -1.0),
max: Vec3d::new(1.0, 1.0, 1.0),
},
radius: 1.732,
};
legion_file.m2_visibility.push(visibility);
let wmo_placement = M2Placement {
id: 2,
m2_id: 5678, position: Vec3d::new(4.0, 5.0, 6.0),
rotation: Vec3d::new(0.0, 0.0, 0.0),
scale: 1.0,
flags: 0,
};
legion_file.wmo_legion_placements.push(wmo_placement);
let wmo_visibility = M2VisibilityInfo {
bounds: BoundingBox {
min: Vec3d::new(-2.0, -2.0, -2.0),
max: Vec3d::new(2.0, 2.0, 2.0),
},
radius: 3.464,
};
legion_file.wmo_legion_visibility.push(wmo_visibility);
let result = convert_wdl_file(&legion_file, WdlVersion::Wotlk);
assert!(result.is_ok());
let wotlk_file = result.unwrap();
assert_eq!(wotlk_file.version, WdlVersion::Wotlk);
assert_eq!(wotlk_file.wmo_filenames.len(), 1);
assert_eq!(wotlk_file.wmo_indices.len(), 1);
assert_eq!(wotlk_file.wmo_placements.len(), 1);
assert!(wotlk_file.wmo_filenames[0].contains("5678"));
let wmo = &wotlk_file.wmo_placements[0];
assert_eq!(wmo.position.x, 4.0);
assert_eq!(wmo.position.y, 5.0);
assert_eq!(wmo.position.z, 6.0);
}
}