1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-07-20 07:47:45 +00:00

Fix remaining issues with parsing PCBs

After looking at the FFXIVClientStructs definition for these, I
figured out the remaining issues. It mostly came down to
misunderstanding what the heck this thing was??
This commit is contained in:
Joshua Goins 2025-07-09 14:57:21 -04:00
parent dbad354cb5
commit fe8afff1c3

View file

@ -8,75 +8,104 @@ use crate::ByteSpan;
use binrw::BinRead; use binrw::BinRead;
use binrw::BinResult; use binrw::BinResult;
use binrw::binrw; use binrw::binrw;
use binrw::helpers::until_eof;
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
#[brw(little)] #[brw(little)]
struct PcbResourceHeader { struct PcbResourceHeader {
magic: u32, // pretty terrible magic if you ask me, lumina calls it so but it's just 0000 pcb_type: u32, // Lumina: 0x0 is resource, 0x1 is list?
version: u32, // usually 0x1? version: u32, // ClientStructs: 0 is 'legacy', 1/4 are 'normal', rest unsupported
total_nodes: u32, total_nodes: u32,
total_polygons: u32, total_polygons: u32,
} }
// TODO: this is adapted from lumina and could probably be implemented better
#[binrw::parser(reader)] #[binrw::parser(reader)]
fn parse_resource_node_children( fn parse_resource_node_children(
group_length: u32, child1_offset: u32,
header_skip: u32, child2_offset: u32,
) -> BinResult<Vec<ResourceNode>> { ) -> BinResult<Vec<ResourceNode>> {
if group_length == 0 { let initial_position = reader.stream_position().unwrap();
return Ok(Vec::default()); let struct_start = initial_position - ResourceNode::HEADER_SIZE as u64;
}
assert!(header_skip > 0);
let mut children = Vec::new(); let mut children = Vec::new();
let initial_position = reader.stream_position().unwrap() - header_skip as u64; if child1_offset != 0 {
let final_position = initial_position + group_length as u64; reader
.seek(SeekFrom::Start(struct_start + child1_offset as u64))
while reader.stream_position().unwrap() + (header_skip as u64) < final_position { .unwrap();
children.push(ResourceNode::read_le(reader).unwrap()); children.push(ResourceNode::read_le(reader).unwrap());
} }
reader.seek(SeekFrom::Start(final_position)).unwrap(); if child2_offset != 0 {
reader
.seek(SeekFrom::Start(struct_start + child2_offset as u64))
.unwrap();
children.push(ResourceNode::read_le(reader).unwrap());
}
Ok(children) Ok(children)
} }
/// Transform compressed vertices from 0-65535 to local_bounds.min-local_bounds.max
fn uncompress_vertices(local_bounds: &AABB, vertex: &[u16; 3]) -> [f32; 3] {
let x_scale = (local_bounds.max[0] - local_bounds.min[0]) / u16::MAX as f32;
let y_scale = (local_bounds.max[1] - local_bounds.min[1]) / u16::MAX as f32;
let z_scale = (local_bounds.max[2] - local_bounds.min[2]) / u16::MAX as f32;
[
local_bounds.min[0] + x_scale * (vertex[0] as f32),
local_bounds.min[1] + y_scale * (vertex[1] as f32),
local_bounds.min[2] + z_scale * (vertex[2] as f32),
]
}
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
#[brw(little)] #[brw(little)]
pub struct ResourceNode { pub struct ResourceNode {
// TODO: figure out what these two values are
magic: u32, // pretty terrible magic if you ask me, lumina calls it so magic: u32, // pretty terrible magic if you ask me, lumina calls it so
version: u32, // usually 0x0, is this really a version?! version: u32, // usually 0x0, is this really a version?!
header_skip: u32,
group_length: u32, child1_offset: u32,
pub bounding_box: BoundingBox, child2_offset: u32,
/// The bounding box of this node.
pub local_bounds: AABB,
num_vert_f16: u16, num_vert_f16: u16,
num_polygons: u16, num_polygons: u16,
#[brw(pad_after = 2)] // padding #[brw(pad_after = 2)] // padding, supposedly
num_vert_f32: u16, num_vert_f32: u16,
#[br(parse_with = parse_resource_node_children, args(group_length, header_skip))] /// The children of this node.
#[br(parse_with = parse_resource_node_children, args(child1_offset, child2_offset))]
#[br(restore_position)]
pub children: Vec<ResourceNode>, pub children: Vec<ResourceNode>,
// TODO: combine these
#[br(count = num_vert_f32)] #[br(count = num_vert_f32)]
pub f32_vertices: Vec<[f32; 3]>, f32_vertices: Vec<[f32; 3]>,
#[br(count = num_vert_f16)] #[br(count = num_vert_f16)]
pub f16_vertices: Vec<[u16; 3]>, #[bw(ignore)]
f16_vertices: Vec<[u16; 3]>,
/// This node's vertices.
#[br(calc = f32_vertices.clone().into_iter().chain(f16_vertices.iter().map(|vec| uncompress_vertices(&local_bounds, vec))).collect())]
#[bw(ignore)]
pub vertices: Vec<[f32; 3]>,
/// This node's polygons, which include index data.
#[br(count = num_polygons)] #[br(count = num_polygons)]
pub polygons: Vec<Polygon>, pub polygons: Vec<Polygon>,
} }
// TODO: de-duplicate with MDL? impl ResourceNode {
pub const HEADER_SIZE: usize = 0x30;
}
#[binrw] #[binrw]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct BoundingBox { pub struct AABB {
pub min: [f32; 3], pub min: [f32; 3],
pub max: [f32; 3], pub max: [f32; 3],
} }
@ -84,10 +113,10 @@ pub struct BoundingBox {
#[binrw] #[binrw]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
struct Polygon { pub struct Polygon {
#[brw(pad_after = 1)] // padding
pub vertex_indices: [u8; 3], pub vertex_indices: [u8; 3],
#[brw(pad_before = 2, pad_after = 5)] // padding pub material: u64,
unk1: u16,
} }
#[binrw] #[binrw]
@ -95,9 +124,8 @@ struct Polygon {
#[brw(little)] #[brw(little)]
pub struct Pcb { pub struct Pcb {
header: PcbResourceHeader, header: PcbResourceHeader,
// NOTE: this is technically wrong, we should be counting each node until we get total_nodes but im lazy /// The root node of this PCB.
#[br(parse_with = until_eof)] pub root_node: ResourceNode,
pub children: Vec<ResourceNode>,
} }
impl Pcb { impl Pcb {