use std::{collections::BTreeSet, io::Cursor, path::Path, sync::Mutex};
use glam::{Mat4, Vec3};
use image_dds::Surface;
use indexmap::IndexMap;
use log::error;
use rayon::prelude::*;
use xc3_lib::{
error::DecompressStreamError,
map::{FoliageMaterials, PropInstance, PropLod, PropModelLod, PropPositions},
mibl::Mibl,
msmd::{ChannelType, MapParts, Msmd, MsmdV112, StreamEntry, legacy::MsmdV11},
mxmd::{MaterialTechniqueType, StateFlags, TextureUsage},
};
use crate::{
IndexMapExt, MapRoot, Material, Model, ModelBuffers, ModelGroup, ModelRoot, Models, Texture,
create_materials,
error::{CreateImageTextureError, LoadMapError},
material::create_materials_samplers_legacy,
model::import::{create_samplers, lod_data},
shader_database::ShaderDatabase,
skinning::create_skinning,
texture::ImageTexture,
};
#[tracing::instrument(skip_all)]
pub fn load_map<P: AsRef<Path>>(
wismhd_path: P,
shader_database: Option<&ShaderDatabase>,
) -> Result<Vec<MapRoot>, LoadMapError> {
let wismhd_path = wismhd_path.as_ref();
let msmd = Msmd::from_file(wismhd_path).map_err(LoadMapError::Wismhd)?;
let wismda = std::fs::read(wismhd_path.with_extension("wismda"))?;
match &msmd.inner {
xc3_lib::msmd::MsmdInner::V11(msmd) => {
MapRoot::from_msmd_v11(msmd, &wismda, shader_database)
}
xc3_lib::msmd::MsmdInner::V112(msmd) => {
MapRoot::from_msmd_v112(msmd, &wismda, shader_database, wismhd_path)
}
}
}
impl MapRoot {
#[tracing::instrument(skip_all)]
pub fn from_msmd_v11(
msmd: &MsmdV11,
wismda: &[u8],
shader_database: Option<&ShaderDatabase>,
) -> Result<Vec<Self>, LoadMapError> {
let compressed = true;
let mut roots = Vec::new();
let texture_indices: Vec<_> = (0..msmd.low_textures.len() as u16).collect();
let texture_cache = TextureCache::from_msmd_v11(msmd, wismda, compressed)?;
let texture_cache = Mutex::new(texture_cache);
let map_model_group = map_models_group_legacy(
msmd,
wismda,
compressed,
&texture_indices,
&texture_cache,
shader_database,
)?;
let prop_model_group = props_group_legacy(
msmd,
wismda,
compressed,
&texture_indices,
&texture_cache,
shader_database,
)?;
let image_textures = texture_cache.into_inner().unwrap().image_textures()?;
roots.push(MapRoot {
groups: vec![map_model_group, prop_model_group],
image_textures,
});
Ok(roots)
}
pub fn from_msmd_v112(
msmd: &MsmdV112,
wismda: &[u8],
shader_database: Option<&ShaderDatabase>,
wismhd_path: &Path,
) -> Result<Vec<Self>, LoadMapError> {
let compressed = msmd.wismda_info.compressed_length != msmd.wismda_info.decompressed_length;
let mut roots = Vec::new();
for model in &msmd.env_models {
let root = load_env_model(wismda, compressed, model, shader_database)?;
roots.push(root);
}
for foliage_model in &msmd.foliage_models {
let root = load_foliage_model(wismda, compressed, foliage_model)?;
roots.push(root);
}
let texture_cache = TextureCache::from_msmd_v112(msmd, wismda, compressed)?;
let texture_cache = Mutex::new(texture_cache);
let map_model_group =
map_models_group(msmd, wismda, compressed, &texture_cache, shader_database)?;
let prop_model_group =
props_group(msmd, wismda, compressed, &texture_cache, shader_database)?;
let image_textures = texture_cache.into_inner().unwrap().image_textures()?;
roots.push(MapRoot {
groups: vec![map_model_group, prop_model_group],
image_textures,
});
if let Some(child_models) = &msmd.child_models {
for model in &child_models.models {
let root = load_child_model(model, child_models, shader_database, wismhd_path)?;
roots.push(root);
}
}
Ok(roots)
}
}
struct TextureCache {
low_textures: Vec<Vec<(TextureUsage, Surface<Vec<u8>>)>>,
high_textures: Vec<Surface<Vec<u8>>>,
texture_to_image_texture_index: IndexMap<TextureKey, usize>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct TextureKey {
low_textures_entry_index: Option<usize>,
low_texture_index: Option<usize>,
texture_index: Option<usize>,
}
impl TextureCache {
#[tracing::instrument(skip_all)]
fn from_msmd_v11(
msmd: &MsmdV11,
wismda: &[u8],
compressed: bool,
) -> Result<Self, LoadMapError> {
let low_textures = msmd
.low_textures
.par_iter()
.map(|e| load_low_textures(wismda, compressed, e))
.collect::<Result<Vec<_>, _>>()?;
let high_textures = msmd
.textures
.par_iter()
.map(|texture| load_high_texture(wismda, compressed, texture))
.collect::<Result<Vec<_>, LoadMapError>>()?;
Ok(Self {
texture_to_image_texture_index: IndexMap::new(),
low_textures,
high_textures,
})
}
#[tracing::instrument(skip_all)]
fn from_msmd_v112(
msmd: &MsmdV112,
wismda: &[u8],
compressed: bool,
) -> Result<Self, LoadMapError> {
let low_textures = msmd
.low_textures
.par_iter()
.map(|e| load_low_textures(wismda, compressed, e))
.collect::<Result<Vec<_>, _>>()?;
let high_textures = msmd
.textures
.par_iter()
.map(|texture| load_high_texture(wismda, compressed, texture))
.collect::<Result<Vec<_>, LoadMapError>>()?;
Ok(Self {
texture_to_image_texture_index: IndexMap::new(),
low_textures,
high_textures,
})
}
fn insert(
&mut self,
texture: &xc3_lib::map::Texture,
low_texture_entry_indices: &[u16],
) -> usize {
let (low_textures_entry_index, low_texture_index) = if texture.flags.has_low_texture() {
let entry_index = usize::try_from(texture.low_textures_entry_index).ok();
let low_textures_entry_index = entry_index
.and_then(|i| low_texture_entry_indices.get(i).map(|i| *i as usize))
.or(entry_index);
let low_texture_index = usize::try_from(texture.low_texture_index).ok();
(low_textures_entry_index, low_texture_index)
} else {
(None, None)
};
let texture_index = if texture.flags.has_high_texture() {
usize::try_from(texture.texture_index).ok()
} else {
None
};
self.texture_to_image_texture_index.entry_index(TextureKey {
low_textures_entry_index,
low_texture_index,
texture_index,
})
}
fn get_low_texture(&self, key: &TextureKey) -> Option<(TextureUsage, Surface<&[u8]>)> {
let entry_index = key.low_textures_entry_index?;
let index = key.low_texture_index?;
self.low_textures
.get(entry_index)?
.get(index)
.map(|(u, t)| (*u, t.as_ref()))
}
fn get_high_texture(&self, key: &TextureKey) -> Option<Surface<&[u8]>> {
let index = key.texture_index?;
self.high_textures.get(index).map(|t| t.as_ref())
}
#[tracing::instrument(skip_all)]
fn image_textures(&self) -> Result<Vec<ImageTexture>, CreateImageTextureError> {
self.texture_to_image_texture_index
.par_iter()
.map(|(texture, _)| self.create_image_texture(texture))
.collect()
}
#[tracing::instrument(skip_all)]
fn create_image_texture(
&self,
texture: &TextureKey,
) -> Result<ImageTexture, CreateImageTextureError> {
let low = self.get_low_texture(texture);
if let Some(surface) = self.get_high_texture(texture).or(low.map(|low| low.1)) {
ImageTexture::from_surface(surface, None, low.map(|l| l.0))
} else {
error!("No mibl for {texture:?}");
let (usage, surface) = &self.low_textures[0][0];
ImageTexture::from_surface(surface.as_ref(), None, Some(*usage))
}
}
}
#[tracing::instrument(skip_all)]
fn load_high_texture(
wismda: &[u8],
compressed: bool,
texture: &xc3_lib::msmd::Texture,
) -> Result<Surface<Vec<u8>>, LoadMapError> {
let mut wismda = Cursor::new(&wismda);
let mibl_m = texture.mid.extract(&mut wismda, compressed)?;
if texture.base_mip.decompressed_size > 0 {
let base_mip_level = texture.base_mip.decompress(&mut wismda, compressed)?;
Ok(mibl_m.to_surface_with_base_mip(&base_mip_level).unwrap())
} else {
Ok(mibl_m.to_surface().unwrap())
}
}
#[tracing::instrument(skip_all)]
fn load_low_textures(
wismda: &[u8],
compressed: bool,
e: &StreamEntry<xc3_lib::msmd::LowTextures>,
) -> Result<Vec<(TextureUsage, Surface<Vec<u8>>)>, LoadMapError> {
let textures = e.extract(&mut Cursor::new(&wismda), compressed)?;
textures
.textures
.iter()
.map(|t| {
Ok((
t.usage,
Mibl::from_bytes(&t.mibl_data)?.to_surface().unwrap(),
))
})
.collect::<Result<Vec<_>, LoadMapError>>()
}
#[tracing::instrument(skip_all)]
fn map_models_group(
msmd: &MsmdV112,
wismda: &[u8],
compressed: bool,
texture_cache: &Mutex<TextureCache>,
shader_database: Option<&ShaderDatabase>,
) -> Result<ModelGroup, LoadMapError> {
let buffers = create_buffers(&msmd.map_vertex_data, wismda, compressed)?;
let map_model_data = msmd
.map_models
.par_iter()
.map(|m| m.entry.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let models = map_model_data
.par_iter()
.map(|model_data| {
let material_root_texture_indices: Vec<_> = model_data
.textures
.iter()
.map(|t| {
texture_cache
.lock()
.unwrap()
.insert(t, &model_data.low_texture_entry_indices)
})
.collect();
load_map_model_group(
model_data,
&material_root_texture_indices,
&model_data.spch,
shader_database,
)
})
.collect();
Ok(ModelGroup { models, buffers })
}
#[tracing::instrument(skip_all)]
fn props_group(
msmd: &MsmdV112,
wismda: &[u8],
compressed: bool,
texture_cache: &Mutex<TextureCache>,
shader_database: Option<&ShaderDatabase>,
) -> Result<ModelGroup, LoadMapError> {
let buffers = create_buffers(&msmd.prop_vertex_data, wismda, compressed)?;
let prop_positions: Vec<_> = msmd
.prop_positions
.par_iter()
.map(|p| p.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let prop_model_data: Vec<_> = msmd
.prop_models
.par_iter()
.map(|m| m.entry.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let models = prop_model_data
.par_iter()
.map(|model_data| {
let material_root_texture_indices: Vec<_> = model_data
.textures
.iter()
.map(|t| {
texture_cache
.lock()
.unwrap()
.insert(t, &model_data.low_texture_entry_indices)
})
.collect();
load_prop_model_group(
model_data,
msmd.parts.as_ref(),
&prop_positions,
&material_root_texture_indices,
shader_database,
)
})
.collect();
Ok(ModelGroup { models, buffers })
}
#[tracing::instrument(skip_all)]
fn create_buffers(
vertex_data: &[StreamEntry<xc3_lib::vertex::VertexData>],
wismda: &[u8],
compressed: bool,
) -> Result<Vec<ModelBuffers>, DecompressStreamError> {
vertex_data
.par_iter()
.map(|e| {
let vertex_data = e.extract(&mut Cursor::new(wismda), compressed)?;
ModelBuffers::from_vertex_data(&vertex_data).map_err(Into::into)
})
.collect()
}
#[tracing::instrument(skip_all)]
fn load_prop_model_group(
model_data: &xc3_lib::map::PropModelData,
parts: Option<&MapParts>,
prop_positions: &[PropPositions],
material_root_texture_indices: &[usize],
shader_database: Option<&ShaderDatabase>,
) -> Models {
let mut model_instances = vec![Vec::new(); model_data.models.models.len()];
add_prop_instances(
&mut model_instances,
&model_data.lods.props,
&model_data.lods.lods,
&model_data.lods.instances,
);
for info in &model_data.prop_info {
let additional_instances = &prop_positions[info.prop_position_entry_index as usize];
add_prop_instances(
&mut model_instances,
&model_data.lods.props,
&model_data.lods.lods,
&additional_instances.instances,
);
}
if let Some(parts) = parts {
add_animated_part_instances(
&mut model_instances,
&model_data.lods.props,
model_data.lods.animated_parts_start_index as usize,
model_data.lods.animated_parts_count as usize,
parts,
);
}
let mut materials = create_materials(
&model_data.materials,
None,
&model_data.spch,
shader_database,
);
apply_material_texture_indices(&mut materials, material_root_texture_indices);
let samplers = create_samplers(&model_data.materials);
let mut models = Models {
models: Vec::new(),
materials,
samplers,
skinning: model_data.models.skinning.as_ref().map(create_skinning),
lod_data: model_data.models.lod_data.as_ref().map(lod_data),
morph_controller_names: Vec::new(),
animation_morph_names: Vec::new(),
min_xyz: model_data.models.min_xyz.into(),
max_xyz: model_data.models.max_xyz.into(),
};
let base_lods: BTreeSet<_> = model_data
.lods
.props
.iter()
.map(|p| p.base_lod_index)
.collect();
for (i, ((model, vertex_data_index), instances)) in model_data
.models
.models
.iter()
.zip(model_data.model_vertex_data_indices.iter())
.zip(model_instances.into_iter())
.enumerate()
{
if base_lods.contains(&(i as u32)) {
let group = Model::from_model_v112(
model,
instances,
*vertex_data_index as usize,
model_data.models.alpha_table.as_ref(),
);
models.models.push(group);
}
}
models
}
fn add_prop_instances(
model_instances: &mut [Vec<Mat4>],
prop_lods: &[PropLod],
model_lods: &[PropModelLod],
instances: &[PropInstance],
) {
for instance in instances {
let prop_lod = &prop_lods[instance.prop_lod_index as usize];
let base_lod_index = (prop_lod.base_lod_index & 0xFFFFFFF) as usize;
let model_lod = &model_lods[base_lod_index];
let model_index = (model_lod.model_index & 0xFFFFFF) as usize;
model_instances[model_index].push(Mat4::from_cols_array_2d(&instance.transform));
}
}
fn add_animated_part_instances(
model_instances: &mut [Vec<Mat4>],
props: &[PropLod],
start_index: usize,
count: usize,
parts: &MapParts,
) {
for i in start_index..start_index + count {
let instance = &parts.animated_instances[i];
let animation = &parts.instance_animations[i];
let mut transform = Mat4::from_cols_array_2d(&instance.transform);
let mut translation: Vec3 = animation.translation.into();
let mut scale = Vec3::ONE;
let mut rot_x = 0.0;
let mut rot_y = 0.0;
let mut rot_z = 0.0;
for channel in &animation.channels {
match channel.channel_type {
ChannelType::TranslationX => {
translation.x += channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::TranslationY => {
translation.y += channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::TranslationZ => {
translation.z += channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::RotationX => {
rot_x = channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::RotationY => {
rot_y = channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::RotationZ => {
rot_z = channel
.keyframes
.first()
.map(|f| f.value)
.unwrap_or_default()
}
ChannelType::ScaleX => {
scale.x = channel.keyframes.first().map(|f| f.value).unwrap_or(1.0)
}
ChannelType::ScaleY => {
scale.y = channel.keyframes.first().map(|f| f.value).unwrap_or(1.0)
}
ChannelType::ScaleZ => {
scale.z = channel.keyframes.first().map(|f| f.value).unwrap_or(1.0)
}
}
}
transform = Mat4::from_translation(translation)
* Mat4::from_euler(glam::EulerRot::XYZ, rot_x, rot_y, rot_z)
* Mat4::from_scale(scale)
* transform;
let model_index = props[instance.prop_lod_index as usize].base_lod_index;
model_instances[model_index as usize].push(transform);
}
}
#[tracing::instrument(skip_all)]
fn load_map_model_group(
model_data: &xc3_lib::map::MapModelData,
material_root_texture_indices: &[usize],
spch: &xc3_lib::spch::Spch,
shader_database: Option<&ShaderDatabase>,
) -> Models {
let mut materials = create_materials(&model_data.materials, None, spch, shader_database);
apply_material_texture_indices(&mut materials, material_root_texture_indices);
let samplers = create_samplers(&model_data.materials);
let models = model_data
.groups
.model_group_index
.iter()
.zip(model_data.models.models.iter())
.filter_map(|(group_index, model)| {
model_data
.groups
.groups
.get(*group_index as usize)
.map(|group| {
let vertex_data_index = group.vertex_data_index as usize;
Model::from_model_v112(
model,
vec![Mat4::IDENTITY],
vertex_data_index,
model_data.models.alpha_table.as_ref(),
)
})
})
.collect();
Models {
models,
materials,
samplers,
skinning: model_data.models.skinning.as_ref().map(create_skinning),
lod_data: model_data.models.lod_data.as_ref().map(lod_data),
morph_controller_names: Vec::new(),
animation_morph_names: Vec::new(),
min_xyz: model_data.models.min_xyz.into(),
max_xyz: model_data.models.max_xyz.into(),
}
}
fn load_env_model(
wismda: &[u8],
compressed: bool,
model: &xc3_lib::msmd::EnvModel,
shader_database: Option<&ShaderDatabase>,
) -> Result<MapRoot, LoadMapError> {
let mut wismda = Cursor::new(&wismda);
let model_data = model.entry.extract(&mut wismda, compressed)?;
let image_textures = model_data
.textures
.textures
.iter()
.map(ImageTexture::from_packed_texture)
.collect::<Result<Vec<_>, _>>()?;
let buffers = ModelBuffers::from_vertex_data(&model_data.vertex_data)?;
Ok(MapRoot {
groups: vec![ModelGroup {
models: vec![Models::from_models_v112(
&model_data.models,
&model_data.materials,
None,
&model_data.spch,
shader_database,
)],
buffers: vec![buffers],
}],
image_textures,
})
}
fn load_foliage_model(
wismda: &[u8],
compressed: bool,
model: &xc3_lib::msmd::FoliageModel,
) -> Result<MapRoot, LoadMapError> {
let mut wismda = Cursor::new(&wismda);
let model_data = model.entry.extract(&mut wismda, compressed)?;
let image_textures = model_data
.textures
.textures
.iter()
.map(ImageTexture::from_packed_texture)
.collect::<Result<Vec<_>, _>>()?;
let materials = foliage_materials(&model_data.materials);
let models = model_data
.models
.models
.iter()
.map(|model| {
Model::from_model_v112(
model,
vec![Mat4::IDENTITY],
0,
model_data.models.alpha_table.as_ref(),
)
})
.collect();
let buffers = ModelBuffers::from_vertex_data(&model_data.vertex_data)?;
Ok(MapRoot {
groups: vec![ModelGroup {
models: vec![Models {
models,
materials,
samplers: Vec::new(),
skinning: model_data.models.skinning.as_ref().map(create_skinning),
lod_data: model_data.models.lod_data.as_ref().map(lod_data),
morph_controller_names: Vec::new(),
animation_morph_names: Vec::new(),
min_xyz: model_data.models.min_xyz.into(),
max_xyz: model_data.models.max_xyz.into(),
}],
buffers: vec![buffers],
}],
image_textures,
})
}
fn foliage_materials(materials: &FoliageMaterials) -> Vec<Material> {
materials
.materials
.iter()
.map(|material| {
let textures = vec![Texture {
image_texture_index: 0,
mipmap_sampler_index: 0,
sampler_index: 0,
}];
let shader = None;
let flags = StateFlags {
depth_write_mode: 0,
blend_mode: xc3_lib::mxmd::BlendMode::Disabled,
cull_mode: xc3_lib::mxmd::CullMode::Disabled,
unk4: 0,
stencil_value: xc3_lib::mxmd::StencilValue::Unk0,
stencil_mode: xc3_lib::mxmd::StencilMode::Unk0,
depth_func: xc3_lib::mxmd::DepthFunc::LessEqual,
color_write_mode: xc3_lib::mxmd::ColorWriteMode::Unk0,
};
Material {
name: material.name.clone(),
flags: xc3_lib::mxmd::MaterialFlags::from(0u32),
render_flags: xc3_lib::mxmd::MaterialRenderFlags::from(0u32),
state_flags: flags,
color: [1.0; 4],
textures,
alt_textures: None,
alpha_test: None,
shader,
alpha_test_ref: 0.5,
technique_index: 0,
technique_type: MaterialTechniqueType::Opaque,
parameters: Default::default(),
work_values: Vec::new(),
variables: Vec::new(),
work_callbacks: Vec::new(),
m_unks1_1: 0,
m_unks1_2: 0,
m_unks1_3: 0,
m_unks1_4: 0,
m_unks2: 0,
gbuffer_flags: 0,
fur_params: None,
}
})
.collect()
}
fn apply_material_texture_indices(
materials: &mut Vec<Material>,
material_root_texture_indices: &[usize],
) {
for material in materials {
for texture in &mut material.textures {
let index = material_root_texture_indices[texture.image_texture_index];
texture.image_texture_index = index;
}
}
}
fn load_child_model(
child_model: &xc3_lib::msmd::MapChildModel,
child_models: &xc3_lib::msmd::MapChildModels,
shader_database: Option<&ShaderDatabase>,
wismhd_path: &Path,
) -> Result<MapRoot, LoadMapError> {
let wismt_path = wismhd_path
.with_file_name(&child_model.streaming_file_name)
.with_extension("wismt");
let mut model_root = ModelRoot::from_mxmd(
&child_model.mxmd,
&wismt_path,
None,
None,
shader_database,
false,
)
.map_err(LoadMapError::Wimdo)?;
let instance_transforms: Vec<_> = child_models
.instance_transforms
.iter()
.skip(child_model.instances_start_index as usize)
.take(child_model.instances_count as usize)
.map(Mat4::from_cols_array_2d)
.collect();
for model in &mut model_root.models.models {
model.instances = instance_transforms.clone();
}
Ok(MapRoot {
groups: vec![ModelGroup {
models: vec![model_root.models],
buffers: vec![model_root.buffers],
}],
image_textures: model_root.image_textures,
})
}
#[tracing::instrument(skip_all)]
fn map_models_group_legacy(
msmd: &MsmdV11,
wismda: &[u8],
compressed: bool,
texture_indices: &[u16],
texture_cache: &Mutex<TextureCache>,
shader_database: Option<&ShaderDatabase>,
) -> Result<ModelGroup, LoadMapError> {
let buffers = create_buffers_legacy(&msmd.map_vertex_data, wismda, compressed)?;
let map_model_data = msmd
.map_models
.par_iter()
.map(|m| m.entry.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let models = map_model_data
.par_iter()
.map(|model_data| {
let material_root_texture_indices: Vec<_> = model_data
.textures
.iter()
.map(|t| {
texture_cache
.lock()
.unwrap()
.insert(t, &model_data.low_texture_indices)
})
.collect();
load_map_model_group_legacy(
model_data,
texture_indices,
&material_root_texture_indices,
shader_database,
)
})
.collect();
Ok(ModelGroup { models, buffers })
}
#[tracing::instrument(skip_all)]
fn load_map_model_group_legacy(
model_data: &xc3_lib::map::legacy::MapModelData,
texture_indices: &[u16],
material_root_texture_indices: &[usize],
shader_database: Option<&ShaderDatabase>,
) -> Models {
let (mut materials, samplers) = create_materials_samplers_legacy(
&model_data.materials,
texture_indices,
model_data.spco.items.first().map(|i| &i.spch),
shader_database,
);
apply_material_texture_indices(&mut materials, material_root_texture_indices);
let models = model_data
.unk8
.items2
.iter()
.zip(model_data.models.models.iter())
.filter_map(|(group_index, model)| {
let item1_index = *group_index as usize % model_data.unk8.items1.len();
let lod_index = *group_index as usize / model_data.unk8.items1.len();
let item1 = &model_data.unk8.items1[item1_index];
let vertex_data_index = item1.unk1[lod_index] as usize;
if lod_index == 0 {
Some(Model::from_model_legacy(
model,
vec![Mat4::IDENTITY],
vertex_data_index,
))
} else {
None
}
})
.collect();
Models {
models,
materials,
samplers,
skinning: None,
lod_data: None,
morph_controller_names: Vec::new(),
animation_morph_names: Vec::new(),
min_xyz: model_data.models.min_xyz.into(),
max_xyz: model_data.models.max_xyz.into(),
}
}
#[tracing::instrument(skip_all)]
fn props_group_legacy(
msmd: &MsmdV11,
wismda: &[u8],
compressed: bool,
texture_indices: &[u16],
texture_cache: &Mutex<TextureCache>,
shader_database: Option<&ShaderDatabase>,
) -> Result<ModelGroup, LoadMapError> {
let buffers = create_buffers_legacy(&msmd.prop_vertex_data, wismda, compressed)?;
let prop_positions: Vec<_> = msmd
.prop_positions
.par_iter()
.map(|p| p.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let prop_model_data: Vec<_> = msmd
.prop_models
.par_iter()
.map(|m| m.entry.extract(&mut Cursor::new(wismda), compressed))
.collect::<Result<Vec<_>, _>>()?;
let models = prop_model_data
.par_iter()
.map(|model_data| {
let material_root_texture_indices: Vec<_> = model_data
.textures
.iter()
.map(|t| {
texture_cache
.lock()
.unwrap()
.insert(t, &model_data.low_texture_entry_indices)
})
.collect();
load_prop_model_group_legacy(
model_data,
&prop_positions,
texture_indices,
&material_root_texture_indices,
shader_database,
)
})
.collect();
Ok(ModelGroup { models, buffers })
}
#[tracing::instrument(skip_all)]
fn load_prop_model_group_legacy(
model_data: &xc3_lib::map::legacy::PropModelData,
_prop_positions: &[xc3_lib::map::legacy::PropPositions],
texture_indices: &[u16],
material_root_texture_indices: &[usize],
shader_database: Option<&ShaderDatabase>,
) -> Models {
let mut model_instances = vec![Vec::new(); model_data.models.models.len()];
add_prop_instances_legacy(
&mut model_instances,
&model_data.lods.props,
&model_data.lods.lods,
&model_data.lods.instances,
);
let (mut materials, samplers) = create_materials_samplers_legacy(
&model_data.materials,
texture_indices,
model_data.spco.items.first().map(|i| &i.spch),
shader_database,
);
apply_material_texture_indices(&mut materials, material_root_texture_indices);
let mut models = Models {
models: Vec::new(),
materials,
samplers,
skinning: None,
lod_data: None,
morph_controller_names: Vec::new(),
animation_morph_names: Vec::new(),
min_xyz: model_data.models.min_xyz.into(),
max_xyz: model_data.models.max_xyz.into(),
};
let base_lods: BTreeSet<_> = model_data
.lods
.props
.iter()
.map(|p| p.base_lod_index)
.collect();
for (i, ((model, vertex_data_index), instances)) in model_data
.models
.models
.iter()
.zip(model_data.model_vertex_data_indices.iter())
.zip(model_instances.into_iter())
.enumerate()
{
if base_lods.contains(&(i as u32)) {
let group = Model::from_model_legacy(model, instances, *vertex_data_index as usize);
models.models.push(group);
}
}
models
}
fn add_prop_instances_legacy(
model_instances: &mut [Vec<Mat4>],
prop_lods: &[xc3_lib::map::legacy::PropLod],
model_lods: &[xc3_lib::map::legacy::PropModelLod],
instances: &[xc3_lib::map::legacy::PropInstance],
) {
for instance in instances {
let prop_lod = &prop_lods[instance.prop_lod_index as usize];
let base_lod_index = (prop_lod.base_lod_index & 0xFFFFFFF) as usize;
let model_lod = &model_lods[base_lod_index];
let model_index = (model_lod.model_index & 0xFFFFFF) as usize;
model_instances[model_index].push(Mat4::from_cols_array_2d(&instance.transform));
}
}
#[tracing::instrument(skip_all)]
fn create_buffers_legacy(
vertex_data: &[StreamEntry<xc3_lib::mxmd::legacy::VertexData>],
wismda: &[u8],
compressed: bool,
) -> Result<Vec<ModelBuffers>, DecompressStreamError> {
vertex_data
.par_iter()
.map(|e| {
let vertex_data = e.extract(&mut Cursor::new(wismda), compressed)?;
ModelBuffers::from_vertex_data_legacy(&vertex_data, binrw::Endian::Little)
.map_err(Into::into)
})
.collect()
}