Skip to content

Commit

Permalink
quick fixes for model loading
Browse files Browse the repository at this point in the history
  • Loading branch information
ScanMountGoat committed Dec 24, 2023
1 parent ba14273 commit ff52c5f
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 54 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fixed an issue where material parameters were not handled correctly in the shader JSON.
* Fixed an issue where some Xenoblade 3 models used incorrect vertex skinning weights.
* Fixed an issue where Xenoblade 1 DE and Xenoblade 2 models did not load the high resolution base mip level.
* Fixed an issue where map textures did not load the high resolution base mip level.
* Fixed an issue where some Xenoblade 3 DLC maps failed to load due to prop instance indexing issues.
* Fixed an issue where gltf export would fail if the destination folder did not exist.

### Changed
* Improved accuracy for file rebuilding.
* Combined Msrd extract methods into a single `Msrd::extract_files` method for better performance.

## 0.2.0 - 2023-11-22
### Added
* Added animation support to xc3_model.
Expand All @@ -44,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Improved accuracy for file rebuilding.
* Reduced dependencies for various projects.
* Changed animation playback functions to take time in seconds to properly handle animation speed.
* Adjusted how MIBl alignment is handled to ensure the MIBL <-> DDS conversion is always 1:1.
* Adjusted how Mibl alignment is handled to ensure the Mibl <-> DDS conversion is always 1:1.
* Adjusted glTF texture assignment to assume first texture is albedo by default.
* Switched to tracy for viewing traces.
* Adjusted decompiled shader annotation to include uniform buffers fields when possible.
Expand Down
58 changes: 35 additions & 23 deletions xc3_lib/src/msrd/streaming.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Cow;

use image_dds::ddsfile::Dds;
use log::error;

use crate::{
error::DecompressStreamError, mibl::Mibl, mxmd::TextureUsage, spch::Spch, vertex::VertexData,
Expand Down Expand Up @@ -478,11 +479,7 @@ fn write_low_textures(
impl StreamingDataLegacy {
pub fn extract_textures(&self, data: &[u8]) -> Vec<ExtractedTexture<Mibl>> {
// Start with lower resolution textures.
let low_data = self.decompress_stream(
data,
self.low_texture_data_offset,
self.low_texture_data_compressed_size,
);
let low_data = self.low_texture_data(data);

let mut textures: Vec<_> = self
.low_textures
Expand All @@ -507,33 +504,48 @@ impl StreamingDataLegacy {
if let (Some(texture_indices), Some(high_textures)) =
(&self.texture_indices, &self.textures)
{
let high_data = self.decompress_stream(
data,
self.texture_data_offset,
self.texture_data_compressed_size,
);
let high_data = self.high_texture_data(data);

for (i, t) in texture_indices.iter().zip(high_textures.textures.iter()) {
let mibl = Mibl::from_bytes(
&high_data
[t.mibl_offset as usize..t.mibl_offset as usize + t.mibl_length as usize],
)
.unwrap();

textures[*i as usize].high = Some(HighTexture {
mid: mibl,
base_mip: None,
});
// TODO: Are these sometimes base mip levels?
if let Some(bytes) = high_data
.get(t.mibl_offset as usize..t.mibl_offset as usize + t.mibl_length as usize)
{
match Mibl::from_bytes(bytes) {
Ok(mibl) => {
textures[*i as usize].high = Some(HighTexture {
mid: mibl,
base_mip: None,
});
}
Err(e) => error!("Error loading legacy high resolution Mibl: {e}"),
}
} else {
error!("Legacy high resolution Mibl bytes out of range")
}
}
}

textures
}

fn decompress_stream<'a>(&self, data: &'a [u8], offset: u32, size: u32) -> Cow<'a, [u8]> {
let data = &data[offset as usize..offset as usize + size as usize];
fn low_texture_data<'a>(&self, data: &'a [u8]) -> Cow<'a, [u8]> {
match self.flags {
StreamingFlagsLegacy::Uncompressed => Cow::Borrowed(data),
StreamingFlagsLegacy::Uncompressed => {
Cow::Borrowed(&data[self.low_texture_data_offset as usize..])
}
StreamingFlagsLegacy::Xbc1 => {
let xbc1 = Xbc1::from_bytes(data).unwrap();
Cow::Owned(xbc1.decompress().unwrap())
}
}
}

fn high_texture_data<'a>(&self, data: &'a [u8]) -> Cow<'a, [u8]> {
match self.flags {
StreamingFlagsLegacy::Uncompressed => {
Cow::Borrowed(&data[self.texture_data_offset as usize..])
}
StreamingFlagsLegacy::Xbc1 => {
let xbc1 = Xbc1::from_bytes(data).unwrap();
Cow::Owned(xbc1.decompress().unwrap())
Expand Down
7 changes: 6 additions & 1 deletion xc3_lib/src/mxmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,10 @@ pub struct Models {
// TODO: What controls the up to 44 optional bytes?
// TODO: How to estimate models offset from these fields?
// offset 160
// TODO: Investigate extra data for legacy mxmd files.
#[br(args { size: models_offset, base_offset})]
pub extra: ModelsExtraData,
#[br(if(version > 10111))]
pub extra: Option<ModelsExtraData>,
}

// Use an enum since even the largest size can have all offsets as null.
Expand Down Expand Up @@ -1229,6 +1231,7 @@ pub enum TextureUsage {
Col3 = 274726912,
Unk3 = 274857984,
Unk2 = 275775488,
Unk20 = 287309824,
Unk17 = 3276800,
F01 = 403701762, // 3D?
Unk4 = 4194304,
Expand All @@ -1240,6 +1243,8 @@ pub enum TextureUsage {
Col4 = 538968064,
Alp4 = 539099136,
Unk12 = 540147712,
Unk18 = 65537,
Unk19 = 805306368,
Unk5 = 807403520,
Unk10 = 807534592,
VolTex = 811597824,
Expand Down
21 changes: 12 additions & 9 deletions xc3_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,16 +440,19 @@ fn load_streaming_data<'a>(mxmd: &'a Mxmd, wismt_path: &Path, is_pc: bool) -> St
textures: ExtractedTextures::Switch(
mxmd.packed_textures
.as_ref()
.unwrap()
.textures
.iter()
.map(|t| ExtractedTexture {
name: t.name.clone(),
usage: t.usage,
low: Mibl::from_bytes(&t.mibl_data).unwrap(),
high: None,
.map(|textures| {
textures
.textures
.iter()
.map(|t| ExtractedTexture {
name: t.name.clone(),
usage: t.usage,
low: Mibl::from_bytes(&t.mibl_data).unwrap(),
high: None,
})
.collect()
})
.collect(),
.unwrap_or_default(),
),
})
}
Expand Down
50 changes: 37 additions & 13 deletions xc3_model/src/map.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{collections::BTreeMap, io::Cursor, path::Path};
use std::{borrow::Cow, collections::BTreeMap, io::Cursor, path::Path};

use glam::{Mat4, Vec3};
use log::error;
use rayon::prelude::*;
use xc3_lib::{
map::{FoliageMaterials, PropInstance, PropLod, PropPositions},
Expand Down Expand Up @@ -133,8 +134,9 @@ impl TextureCache {
.base_mip
.decompress(&mut wismda, compressed)
.unwrap();

// TODO: Is this the correct way to check for this?
if base_mip_level.is_empty() {
if base_mip_level.is_empty() || texture.flags != 0 {
mibl_m
} else {
mibl_m.with_base_mip(&base_mip_level)
Expand All @@ -150,6 +152,7 @@ impl TextureCache {
}
}
fn insert(&mut self, texture: &xc3_lib::map::Texture) -> usize {
// TODO: How is this supposed to work?
let key = (
texture.low_texture_index,
texture.low_textures_entry_index,
Expand All @@ -158,20 +161,36 @@ impl TextureCache {
match self.texture_to_image_texture_index.get(&key) {
Some(index) => *index,
None => {
let next_index = self.image_textures.len();

let low = self
.get_low_texture(texture.low_texture_index, texture.low_textures_entry_index);
// TODO: How to handle negative indices?
let mibl = &self.high_textures[texture.texture_index.max(0) as usize];

let image_texture =
ImageTexture::from_mibl(mibl, None, low.map(|l| l.usage)).unwrap();
self.image_textures.push(image_texture);

self.texture_to_image_texture_index.insert(key, next_index);

next_index
if let Some(mibl) = self
.get_high_texture(texture.texture_index)
.map(Cow::Borrowed)
.or_else(|| {
low.map(|low| Cow::Owned(Mibl::from_bytes(&low.mibl_data).unwrap()))
})
{
match ImageTexture::from_mibl(&mibl, None, low.map(|l| l.usage)) {
Ok(image_texture) => {
let next_index = self.image_textures.len();

self.image_textures.push(image_texture);

self.texture_to_image_texture_index.insert(key, next_index);
next_index
}
Err(e) => {
// TODO: merging base mip is sometimes broken?
error!("Error deswizzling Mibl: {e}");
0
}
}
} else {
// TODO: What do do if both indices are negative?
error!("No mibl for texture: {texture:?}");
0
}
}
}
}
Expand All @@ -181,6 +200,11 @@ impl TextureCache {
let index = usize::try_from(index).ok()?;
self.low_textures.get(entry_index)?.textures.get(index)
}

fn get_high_texture(&self, index: i16) -> Option<&Mibl> {
let index = usize::try_from(index).ok()?;
self.high_textures.get(index)
}
}

fn map_models_group(
Expand Down
6 changes: 3 additions & 3 deletions xc3_model/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ impl Material {
})
};

// TODO: Why does this sometimes index out of range?
let color_index = self.textures.iter().position(|t| {
matches!(
textures[t.image_texture_index].usage,
// TODO: Why does this index out of range for xc2 legacy mxmd?
textures.get(t.image_texture_index).and_then(|t| t.usage),
Some(
TextureUsage::Col
| TextureUsage::Col2
Expand All @@ -262,7 +262,7 @@ impl Material {
// This may only have two channels since BC5 is common.
let normal_index = self.textures.iter().position(|t| {
matches!(
textures[t.image_texture_index].usage,
textures.get(t.image_texture_index).and_then(|t| t.usage),
Some(TextureUsage::Nrm | TextureUsage::Nrm2)
)
});
Expand Down
11 changes: 7 additions & 4 deletions xc3_wgpu/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,16 @@ fn material_texture<'a>(
.get(index)
.and_then(|texture| textures.get(texture.image_texture_index))
.and_then(|texture| {
// TODO: How to handle 3D textures within the shader?
if texture.dimension() == wgpu::TextureDimension::D2 {
// TODO: How to handle 3D textures and cube maps within the shader?
if texture.dimension() == wgpu::TextureDimension::D2
&& texture.depth_or_array_layers() == 1
{
Some(texture)
} else {
error!(
"Expected 2D texture but found dimension {:?}.",
texture.dimension()
"Expected 2D texture but found dimension {:?} and {} layers.",
texture.dimension(),
texture.depth_or_array_layers()
);
None
}
Expand Down
1 change: 1 addition & 0 deletions xc3_wgpu_batch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ fn main() {
let paths = [
// XC1
"pc062700.wimdo",
"ma0000.wismhd",
];
if paths.iter().any(|p| model_path.ends_with(p)) {
return;
Expand Down

0 comments on commit ff52c5f

Please sign in to comment.