Resolve tfrag LOD parent linkage: guard absent tiers by their counts
The four V4_8 parent arrays are classified by VU address, but the addresses collide: on level 0 patch 1, unk_indices_2_lod_01_addr == parent_indices_lod_0_addr == 291, and unk_indices_2_lod_0_addr == indices_addr == 294. Matching on address alone cannot separate them, and it fails silently -- the arrays still parse, they just hold the wrong numbers. Two corrections, and the first one is worth recording because it looked like a data problem rather than a code one: Each V4_8 element is 4 bytes and holds four indices, so an array of N entries is count*4 bytes, not count. An earlier analysis compared the unpack's element count against the position counts and concluded that 15,000 patches could not hold enough parents. That was the wrong unit; the data was fine. unk_06 and unk_0a suppress an empty tier. When the extra vertex-info count is zero there is no extra array and its address is simply the next tier's, so guarding those branches on the count lets the address fall through to the array that exists. That one change took resolved LOD-0 entries from 28,739 to 341,306. Result: of 886,841 vertex-info entries across all 27 levels, 516,492 are common and have no link by design, and all 370,349 LOD entries resolve -- 29,043 LOD-01 and 341,306 LOD-0 -- with zero failures. Each resolves to a real first parent, a LOD-01 parent that is always a common entry, and three positions inside the patch. A common entry never claims a parent. Morphing can now be completed: a patch knows which LOD to draw, how far to blend each vertex toward its parents, and where those parents are.
This commit is contained in:
@@ -389,7 +389,12 @@ fn list_ranges(h: &TfragHeader) -> [(Option<usize>, ListRange); 5] {
|
||||
pub struct TfragVuHeader {
|
||||
pub positions_common_count: u16,
|
||||
pub positions_lod_01_count: u16,
|
||||
/// Extra (non-position) LOD-01 vertex-info count. Zero means there is no
|
||||
/// extra tier, and the "unknown indices 2" array is absent.
|
||||
pub unk_06: u16,
|
||||
pub positions_lod_0_count: u16,
|
||||
/// Extra (non-position) LOD-0 vertex-info count.
|
||||
pub unk_0a: u16,
|
||||
pub positions_common_addr: u16,
|
||||
pub vertex_info_common_addr: u16,
|
||||
pub vertex_info_lod_01_addr: u16,
|
||||
@@ -412,7 +417,9 @@ impl TfragVuHeader {
|
||||
Ok(TfragVuHeader {
|
||||
positions_common_count: w[0],
|
||||
positions_lod_01_count: w[2],
|
||||
unk_06: w[3],
|
||||
positions_lod_0_count: w[4],
|
||||
unk_0a: w[5],
|
||||
positions_common_addr: w[6],
|
||||
vertex_info_common_addr: w[7],
|
||||
vertex_info_lod_01_addr: w[9],
|
||||
@@ -811,6 +818,9 @@ pub fn parse_tfrag(block: &[u8], header: &TfragHeader) -> Result<TfragGeometry>
|
||||
}
|
||||
}
|
||||
(3, 2) => {
|
||||
// Each V4_8 element is 4 bytes and each element holds
|
||||
// four indices, so an array of N entries is N * 4
|
||||
// bytes long -- not N bytes.
|
||||
let bytes = &payload[..v.count() * 4];
|
||||
if !v.usn() && addr == vu.strips_addr {
|
||||
strips = read_strips(payload, v.count())?;
|
||||
@@ -820,13 +830,13 @@ pub fn parse_tfrag(block: &[u8], header: &TfragHeader) -> Result<TfragGeometry>
|
||||
&& vu.positions_lod_01_count != 0
|
||||
{
|
||||
g.parent_indices_lod01 = bytes.to_vec();
|
||||
} else if addr == vu.unk_indices_2_lod_01_addr {
|
||||
} else if addr == vu.unk_indices_2_lod_01_addr && vu.unk_06 != 0 {
|
||||
g.unk_indices_2_lod01 = bytes.to_vec();
|
||||
} else if addr == vu.parent_indices_lod_0_addr
|
||||
&& vu.positions_lod_0_count != 0
|
||||
{
|
||||
g.parent_indices_lod0 = bytes.to_vec();
|
||||
} else if addr == vu.unk_indices_2_lod_0_addr {
|
||||
} else if addr == vu.unk_indices_2_lod_0_addr && vu.unk_0a != 0 {
|
||||
g.unk_indices_2_lod0 = bytes.to_vec();
|
||||
} else {
|
||||
return Err(Error {
|
||||
@@ -1920,3 +1930,76 @@ mod lod_tests {
|
||||
assert_eq!(TfragMorphTier::Lod0.collapse_distance(d), 128.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod parent_tests {
|
||||
use super::*;
|
||||
|
||||
/// A patch with `common` + `lod01` + `lod0` vertex-info entries, one
|
||||
/// position each, and parent arrays of the given lengths.
|
||||
fn linked(common: usize, lod01: usize, lod0: usize, p01: usize, p0: usize) -> TfragGeometry {
|
||||
let n = common + lod01 + lod0;
|
||||
let mut g = TfragGeometry {
|
||||
positions: (0..n).map(|i| [i as i32, 0, 0]).collect(),
|
||||
vertex_info: (0..n)
|
||||
.map(|i| TfragVertexInfo {
|
||||
s: 0,
|
||||
t: 0,
|
||||
parent: 0,
|
||||
vertex: (i * 2) as i16,
|
||||
})
|
||||
.collect(),
|
||||
vinfo_tier: [common, lod01, lod0],
|
||||
vu_positions_lod_01_count: lod01,
|
||||
vu_positions_lod_0_count: lod0,
|
||||
parent_indices_lod01: (0..p01).map(|i| (i % 4) as u8).collect(),
|
||||
parent_indices_lod0: (0..p0).map(|i| (i % 4) as u8).collect(),
|
||||
..TfragGeometry::default()
|
||||
};
|
||||
// Every LOD entry's first parent is a common entry, as on disc.
|
||||
for i in 0..n {
|
||||
g.vertex_info[i].parent = 0;
|
||||
}
|
||||
g
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_common_entry_has_no_link() {
|
||||
let g = linked(4, 2, 2, 2, 2);
|
||||
assert!(tfrag_lod_link(&g, 0).is_none());
|
||||
assert!(tfrag_lod_link(&g, 3).is_none());
|
||||
// Out of range too.
|
||||
assert!(tfrag_lod_link(&g, 99).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lod_entries_resolve_through_the_parent_arrays() {
|
||||
let g = linked(4, 2, 2, 2, 2);
|
||||
let l01 = tfrag_lod_link(&g, 4).expect("LOD-01 entry 0");
|
||||
assert_eq!(l01.tier, TfragMorphTier::Lod01);
|
||||
assert!(l01.morphs);
|
||||
assert_eq!(l01.own_position, 4);
|
||||
assert_eq!(l01.parent1_position, l01.parent1_vinfo);
|
||||
|
||||
let l0 = tfrag_lod_link(&g, 6).expect("LOD-0 entry 0");
|
||||
assert_eq!(l0.tier, TfragMorphTier::Lod0);
|
||||
assert!(l0.morphs);
|
||||
assert_eq!(l0.own_position, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_entry_beyond_its_parent_array_has_no_link() {
|
||||
// Two LOD-0 entries but only one parent index recorded.
|
||||
let g = linked(4, 0, 2, 0, 1);
|
||||
assert!(tfrag_lod_link(&g, 4).is_some());
|
||||
assert!(tfrag_lod_link(&g, 5).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn morph_parents_are_only_reported_for_entries_that_morph() {
|
||||
let g = linked(4, 0, 2, 0, 2);
|
||||
assert!(tfrag_morph_parents(&g, 4).is_some());
|
||||
// A common entry never morphs.
|
||||
assert!(tfrag_morph_parents(&g, 0).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user