use anyhow::{Result, bail};
use std::io::{Read, Seek};
use crate::resource::prp::PlasmaRead;
use crate::core::uoid::{Uoid, read_key_uoid};
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct ClusterVertex {
pub position: [f32; 4],
pub normal: [f32; 4],
pub color: [f32; 4],
pub uv: [f32; 2],
pub uv2: [f32; 2],
pub uv3: [f32; 2],
pub flags: f32,
pub _pad: f32,
pub uv4: [f32; 2],
pub uv5: [f32; 2],
}
#[derive(Debug)]
pub struct ClusterGroup {
pub template: SpanTemplate,
pub material_uoid: Option<Uoid>,
pub clusters: Vec<Cluster>,
}
#[derive(Debug)]
pub struct SpanTemplate {
pub format: u16,
pub num_verts: u16,
pub num_tris: u16,
pub stride: usize,
pub vert_data: Vec<u8>,
pub index_data: Vec<u16>,
}
#[derive(Debug)]
pub struct Cluster {
pub encoding: SpanEncoding,
pub instances: Vec<SpanInstance>,
}
#[derive(Debug, Clone, Copy)]
pub struct SpanEncoding {
pub code: u8,
pub pos_scale: f32,
}
#[derive(Debug)]
pub struct SpanInstance {
pub l2w: [[f32; 4]; 3],
pub pos_delta: Option<Vec<u8>>,
pub col: Option<Vec<u8>>,
}
const POS_NONE: u8 = 0x0;
const POS_888: u8 = 0x1;
const POS_161616: u8 = 0x2;
const POS_101010: u8 = 0x4;
const POS_008: u8 = 0x8;
const POS_MASK: u8 = POS_888 | POS_161616 | POS_101010 | POS_008;
const COL_NONE: u16 = 0x0;
const COL_A8: u16 = 0x10;
const COL_I8: u16 = 0x20;
const COL_AI88: u16 = 0x40;
const COL_RGB888: u16 = 0x80;
const COL_ARGB8888: u16 = 0x100;
const COL_MASK: u16 = COL_A8 | COL_I8 | COL_AI88 | COL_RGB888 | COL_ARGB8888;
const TMPL_POS: u16 = 0x1;
const TMPL_NORM: u16 = 0x2;
const TMPL_COLOR: u16 = 0x4;
const TMPL_WGTIDX: u16 = 0x8;
const TMPL_UVW_MASK: u16 = 0xF0;
const TMPL_WEIGHT_MASK: u16 = 0x300;
const TMPL_COLOR2: u16 = 0x400;
impl SpanEncoding {
fn pos_stride(&self) -> usize {
match self.code & POS_MASK {
POS_888 => 3,
POS_161616 => 6,
POS_101010 => 4,
POS_008 => 1,
_ => 0,
}
}
fn col_stride(&self) -> usize {
let code = self.code as u16;
match code & COL_MASK {
COL_A8 | COL_I8 => 1,
COL_AI88 => 2,
COL_RGB888 => 3,
COL_ARGB8888 => 4,
_ => 0,
}
}
}
impl SpanTemplate {
pub fn num_uvws(&self) -> usize { ((self.format & TMPL_UVW_MASK) >> 4) as usize }
fn num_weights(&self) -> usize { ((self.format & TMPL_WEIGHT_MASK) >> 8) as usize }
fn has_pos(&self) -> bool { self.format & TMPL_POS != 0 }
fn has_norm(&self) -> bool { self.format & TMPL_NORM != 0 }
fn has_color(&self) -> bool { self.format & TMPL_COLOR != 0 }
fn has_color2(&self) -> bool { self.format & TMPL_COLOR2 != 0 }
fn has_wgt_idx(&self) -> bool { self.format & TMPL_WGTIDX != 0 }
fn calc_stride(format: u16) -> usize {
let mut s = 0usize;
if format & TMPL_POS != 0 { s += 12; } let num_weights = ((format & TMPL_WEIGHT_MASK) >> 8) as usize;
s += num_weights * 4; if format & TMPL_WGTIDX != 0 { s += 4; } if format & TMPL_NORM != 0 { s += 12; } if format & TMPL_COLOR != 0 { s += 4; } if format & TMPL_COLOR2 != 0 { s += 4; } let num_uvws = ((format & TMPL_UVW_MASK) >> 4) as usize;
s += num_uvws * 12; s
}
fn pos_offset(&self) -> usize { 0 }
fn norm_offset(&self) -> usize {
let mut o = if self.has_pos() { 12 } else { 0 };
o += self.num_weights() * 4;
if self.has_wgt_idx() { o += 4; }
o
}
fn color_offset(&self) -> usize {
self.norm_offset() + if self.has_norm() { 12 } else { 0 }
}
fn color2_offset(&self) -> usize {
self.color_offset() + if self.has_color() { 4 } else { 0 }
}
fn uvw_offset(&self) -> usize {
self.color2_offset() + if self.has_color2() { 4 } else { 0 }
}
}
impl ClusterGroup {
pub fn read(reader: &mut (impl Read + Seek)) -> Result<Self> {
let _self_key = read_key_uoid(reader)?;
let template = SpanTemplate::read(reader)?;
let material_uoid = read_key_uoid(reader)?;
let num_clusters = reader.read_u32()? as usize;
let mut clusters = Vec::with_capacity(num_clusters);
for _ in 0..num_clusters {
clusters.push(Cluster::read(reader, template.num_verts)?);
}
let num_regions = reader.read_u32()? as usize;
for _ in 0..num_regions {
let _ = read_key_uoid(reader)?;
}
let num_lights = reader.read_u32()? as usize;
for _ in 0..num_lights {
let _ = read_key_uoid(reader)?;
}
let _min_dist = reader.read_f32()?;
let _max_dist = reader.read_f32()?;
let _render_level = reader.read_u32()?;
let _ = read_key_uoid(reader)?;
let total_insts: usize = clusters.iter().map(|c| c.instances.len()).sum();
log::debug!("ClusterGroup: {} clusters, {} total instances, template: {} verts, {} tris, format=0x{:X}",
clusters.len(), total_insts, template.num_verts, template.num_tris, template.format);
Ok(Self {
template,
material_uoid,
clusters,
})
}
pub fn unpack_meshes(&self) -> Vec<(Vec<ClusterVertex>, Vec<u16>)> {
let templ = &self.template;
let verts_per_inst = templ.num_verts as usize;
let indices_per_inst = templ.num_tris as usize * 3;
let mut result = Vec::new();
for cluster in &self.clusters {
let num_insts = cluster.instances.len();
if num_insts == 0 { continue; }
let mut all_verts = Vec::with_capacity(verts_per_inst * num_insts);
let mut all_indices = Vec::with_capacity(indices_per_inst * num_insts);
let mut idx_offset = 0u16;
for inst in &cluster.instances {
for &idx in &templ.index_data {
all_indices.push(idx + idx_offset);
}
idx_offset += verts_per_inst as u16;
let l2w = &inst.l2w;
let m: [f32; 16] = [
l2w[0][0], l2w[0][1], l2w[0][2], l2w[0][3],
l2w[1][0], l2w[1][1], l2w[1][2], l2w[1][3],
l2w[2][0], l2w[2][1], l2w[2][2], l2w[2][3],
0.0, 0.0, 0.0, 1.0,
];
let w2l_t = transpose_inverse_3x3(&m);
let enc = &cluster.encoding;
let pos_stride = enc.pos_stride();
let col_stride = enc.col_stride();
for vi in 0..verts_per_inst {
let base = vi * templ.stride;
let pos_off = templ.pos_offset();
let (mut px, mut py, mut pz) = if templ.has_pos() && base + pos_off + 12 <= templ.vert_data.len() {
let o = base + pos_off;
(
f32::from_le_bytes(templ.vert_data[o..o+4].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+4..o+8].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+8..o+12].try_into().unwrap()),
)
} else {
(0.0, 0.0, 0.0)
};
if let Some(ref pos_data) = inst.pos_delta {
let delta_off = vi * pos_stride;
let (dx, dy, dz) = decode_pos_delta(enc, pos_data, delta_off);
px += dx;
py += dy;
pz += dz;
}
let wx = px * m[0] + py * m[1] + pz * m[2] + m[3];
let wy = px * m[4] + py * m[5] + pz * m[6] + m[7];
let wz = px * m[8] + py * m[9] + pz * m[10] + m[11];
let norm_off = templ.norm_offset();
let (nx, ny, nz) = if templ.has_norm() && base + norm_off + 12 <= templ.vert_data.len() {
let o = base + norm_off;
(
f32::from_le_bytes(templ.vert_data[o..o+4].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+4..o+8].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+8..o+12].try_into().unwrap()),
)
} else {
(0.0, 1.0, 0.0)
};
let wnx = nx * w2l_t[0] + ny * w2l_t[1] + nz * w2l_t[2];
let wny = nx * w2l_t[3] + ny * w2l_t[4] + nz * w2l_t[5];
let wnz = nx * w2l_t[6] + ny * w2l_t[7] + nz * w2l_t[8];
let nlen = (wnx*wnx + wny*wny + wnz*wnz).sqrt().max(1e-10);
let (r, g, b, a) = if templ.has_color() {
let col_off = templ.color_offset();
let o = base + col_off;
if o + 4 <= templ.vert_data.len() {
let mut c = u32::from_le_bytes(templ.vert_data[o..o+4].try_into().unwrap());
if let Some(ref col_data) = inst.col {
c = decode_color(enc, col_data, vi * col_stride, c);
}
let ca = ((c >> 24) & 0xFF) as f32 / 255.0;
let cr = ((c >> 16) & 0xFF) as f32 / 255.0;
let cg = ((c >> 8) & 0xFF) as f32 / 255.0;
let cb = (c & 0xFF) as f32 / 255.0;
(cr, cg, cb, ca)
} else {
(1.0, 1.0, 1.0, 1.0)
}
} else {
(1.0, 1.0, 1.0, 1.0)
};
let uvw_off = templ.uvw_offset();
let (u, v) = if templ.num_uvws() > 0 {
let o = base + uvw_off;
if o + 8 <= templ.vert_data.len() {
(
f32::from_le_bytes(templ.vert_data[o..o+4].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+4..o+8].try_into().unwrap()),
)
} else {
(0.0, 0.0)
}
} else {
(0.0, 0.0)
};
let (u2, v2) = if templ.num_uvws() > 1 {
let o = base + uvw_off + 12; if o + 8 <= templ.vert_data.len() {
(
f32::from_le_bytes(templ.vert_data[o..o+4].try_into().unwrap()),
f32::from_le_bytes(templ.vert_data[o+4..o+8].try_into().unwrap()),
)
} else {
(0.0, 0.0)
}
} else {
(0.0, 0.0)
};
all_verts.push(ClusterVertex {
position: [wx, wy, wz, 0.0],
normal: [wnx / nlen, wny / nlen, wnz / nlen, 0.0],
color: [r, g, b, a],
uv: [u, v],
uv2: [u2, v2],
uv3: [0.0, 0.0],
flags: 2.0,
_pad: 0.0,
uv4: [0.0, 0.0],
uv5: [0.0, 0.0],
});
}
}
if !all_verts.is_empty() && !all_indices.is_empty() {
result.push((all_verts, all_indices));
}
}
result
}
}
impl SpanTemplate {
fn read(reader: &mut (impl Read + Seek)) -> Result<Self> {
let num_verts = reader.read_u16()?;
let format = reader.read_u16()?;
let num_tris = reader.read_u16()?;
let stride = Self::calc_stride(format);
let vert_size = num_verts as usize * stride;
let idx_size = num_tris as usize * 3;
let mut vert_data = vec![0u8; vert_size];
reader.read_exact(&mut vert_data)?;
let mut index_data = Vec::with_capacity(idx_size);
for _ in 0..idx_size {
index_data.push(reader.read_u16()?);
}
Ok(Self { format, num_verts, num_tris, stride, vert_data, index_data })
}
}
impl Cluster {
fn read(reader: &mut (impl Read + Seek), num_template_verts: u16) -> Result<Self> {
let code = reader.read_u8()?;
let pos_scale = reader.read_f32()?;
let encoding = SpanEncoding { code, pos_scale };
let num_verts = num_template_verts as usize;
let num_insts = reader.read_u32()? as usize;
let mut instances = Vec::with_capacity(num_insts);
let pos_stride = encoding.pos_stride();
let col_stride = encoding.col_stride();
for _ in 0..num_insts {
let mut l2w = [[0.0f32; 4]; 3];
for row in &mut l2w {
for col in row.iter_mut() {
*col = reader.read_f32()?;
}
}
let pos_delta = if pos_stride > 0 {
let size = num_verts * pos_stride;
let mut data = vec![0u8; size];
reader.read_exact(&mut data)?;
Some(data)
} else {
None
};
let col = if col_stride > 0 {
let size = num_verts * col_stride;
let mut data = vec![0u8; size];
reader.read_exact(&mut data)?;
Some(data)
} else {
None
};
instances.push(SpanInstance { l2w, pos_delta, col });
}
Ok(Self { encoding, instances })
}
}
fn decode_pos_delta(enc: &SpanEncoding, data: &[u8], offset: usize) -> (f32, f32, f32) {
match enc.code & POS_MASK {
POS_888 => {
if offset + 3 <= data.len() {
let dx = data[offset] as i8 as f32 * enc.pos_scale;
let dy = data[offset + 1] as i8 as f32 * enc.pos_scale;
let dz = data[offset + 2] as i8 as f32 * enc.pos_scale;
(dx, dy, dz)
} else { (0.0, 0.0, 0.0) }
}
POS_161616 => {
if offset + 6 <= data.len() {
let dx = i16::from_le_bytes(data[offset..offset+2].try_into().unwrap()) as f32 * enc.pos_scale;
let dy = i16::from_le_bytes(data[offset+2..offset+4].try_into().unwrap()) as f32 * enc.pos_scale;
let dz = i16::from_le_bytes(data[offset+4..offset+6].try_into().unwrap()) as f32 * enc.pos_scale;
(dx, dy, dz)
} else { (0.0, 0.0, 0.0) }
}
POS_101010 => {
if offset + 4 <= data.len() {
let packed = u32::from_le_bytes(data[offset..offset+4].try_into().unwrap());
let dx = (packed & 0x3F) as f32 * enc.pos_scale;
let dy = ((packed >> 10) & 0x3F) as f32 * enc.pos_scale;
let dz = ((packed >> 20) & 0x3F) as f32 * enc.pos_scale;
(dx, dy, dz)
} else { (0.0, 0.0, 0.0) }
}
POS_008 => {
if offset < data.len() {
let dz = data[offset] as i8 as f32 * enc.pos_scale;
(0.0, 0.0, dz)
} else { (0.0, 0.0, 0.0) }
}
_ => (0.0, 0.0, 0.0),
}
}
fn decode_color(enc: &SpanEncoding, data: &[u8], offset: usize, template_color: u32) -> u32 {
let code = enc.code as u16;
match code & COL_MASK {
COL_A8 => {
if offset < data.len() {
(template_color & 0x00FFFFFF) | ((data[offset] as u32) << 24)
} else { template_color }
}
COL_I8 => {
if offset < data.len() {
let v = data[offset] as u32;
(template_color & 0xFF000000) | (v << 16) | (v << 8) | v
} else { template_color }
}
COL_AI88 => {
if offset + 2 <= data.len() {
let val = u16::from_le_bytes(data[offset..offset+2].try_into().unwrap());
let col = (val & 0xFF) as u32;
let alpha = ((val & 0xFF00) >> 8) as u32;
(alpha << 24) | (col << 16) | (col << 8) | col
} else { template_color }
}
COL_RGB888 => {
if offset + 3 <= data.len() {
(template_color & 0xFF000000)
| ((data[offset] as u32) << 16)
| ((data[offset + 1] as u32) << 8)
| (data[offset + 2] as u32)
} else { template_color }
}
COL_ARGB8888 => {
if offset + 4 <= data.len() {
u32::from_le_bytes(data[offset..offset+4].try_into().unwrap())
} else { template_color }
}
_ => template_color,
}
}
fn transpose_inverse_3x3(m: &[f32; 16]) -> [f32; 9] {
let a = m[0]; let b = m[1]; let c = m[2];
let d = m[4]; let e = m[5]; let f = m[6];
let g = m[8]; let h = m[9]; let i = m[10];
let det = a*(e*i - f*h) - b*(d*i - f*g) + c*(d*h - e*g);
if det.abs() < 1e-10 {
return [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
}
let inv_det = 1.0 / det;
[
(e*i - f*h) * inv_det, (f*g - d*i) * inv_det, (d*h - e*g) * inv_det,
(c*h - b*i) * inv_det, (a*i - c*g) * inv_det, (b*g - a*h) * inv_det,
(b*f - c*e) * inv_det, (c*d - a*f) * inv_det, (a*e - b*d) * inv_det,
]
}