From bfa1c87d5c92517904ca6ba0afee32d3ecca4d5a Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Fri, 17 Feb 2023 23:19:04 -0800 Subject: [PATCH] Add preliminary CLSN/DCLN conversion --- src/cmd/clsn.rs | 243 +++++++++++++++++++++++++++++++++++++++ src/cmd/cmdl.rs | 25 ++-- src/cmd/mod.rs | 1 + src/main.rs | 2 + src/util/dds.rs | 2 +- src/util/math_classes.rs | 40 +++++++ src/util/mod.rs | 1 + 7 files changed, 296 insertions(+), 18 deletions(-) create mode 100644 src/cmd/clsn.rs create mode 100644 src/util/math_classes.rs diff --git a/src/cmd/clsn.rs b/src/cmd/clsn.rs new file mode 100644 index 0000000..0e7103f --- /dev/null +++ b/src/cmd/clsn.rs @@ -0,0 +1,243 @@ +use std::{ + borrow::Cow, + collections::HashMap, + fs::{ + DirBuilder, + File + }, + io::{Cursor, Read, Write}, + path::PathBuf, + string::FromUtf8Error, +}; + +use anyhow::{bail, ensure, Result}; +use argh::FromArgs; +use binrw::{binrw, BinReaderExt, BinWriterExt, Endian}; + +use crate::{ + format::{ + chunk::ChunkDescriptor, + pack::{K_CHUNK_META, K_FORM_FOOT}, + rfrm::FormDescriptor, + FourCC, + }, + util::{ + file::map_file, + lzss::decompress_buffer, + math_classes::{ + CVector3f, CAABox, COBBox + }, + },}; +use crate::cmd::txtr::K_CHUNK_HEAD; + +// CAABoxCollisionTree +pub const K_FORM_CLSN: FourCC = FourCC(*b"CLSN"); +// COBBoxCollisionTree +pub const K_FORM_DCLN: FourCC = FourCC(*b"DCLN"); + +// COBBCollisionTree Header (only used in DCLN) +pub const K_CHUNK_INFO: FourCC = FourCC(*b"INFO"); + +// Vertex data +pub const K_CHUNK_VERT: FourCC = FourCC(*b"VERT"); + +// Material data +pub const K_CHUNK_MTRL: FourCC = FourCC(*b"MTRL"); + +// Triangle data +pub const K_CHUNK_TRIS: FourCC = FourCC(*b"TRIS"); + +// Octree data +pub const K_CHUNK_TREE: FourCC = FourCC(*b"TREE"); + +pub const K_CLSN_READER_VERSION: u32 = 11; +pub const K_CLSN_WRITER_VERSION: u32 = 22; + +pub const K_DCLN_READER_VERSION: u32 = 9; +pub const K_DCLN_WRITER_VERSION: u32 = 18; + +#[binrw] +#[derive(Clone, Debug)] +pub struct Vertices { + pub count: u32, + #[br(count = count)] + vertices: Vec +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct Materials { + pub count: u32, + #[br(count = count)] + materials: Vec +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct CCollisionMaterial { + orientation: u32, + material_type: u32, + world_type: u32, + behavior_list: u32, + filter_list: u32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct CIndexedTriangle { + idx1: u32, + idx2: u32, + idx3: u32, + material: u16, + unk: u16, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct Triangles { + count: u32, + #[br(count = count)] + triangles: Vec +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct AABoxTreeNode { + bounds: CAABox, + start: u32, + end: u32, + unk1: u8, + unk2: u8, + unk3: u8, + unk4: u8, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct OBBoxTreeNode { + bounds: COBBox, + start: u32, + end: u32, + unk1: u8, + unk2: u8, + unk3: u8, + unk4: u8, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct AABoxCollisionTree { + count: u32, + #[br(count = count)] + nodes: Vec, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct OBBoxCollisionTree { + count: u32, + #[br(count = count)] + nodes: Vec, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// process CLSN/DCLN files +#[argh(subcommand, name = "collision")] +pub struct Args { + #[argh(subcommand)] + command: SubCommand, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubCommand { + Convert(ConvertArgs), +} + +#[derive(FromArgs, PartialEq, Eq, Debug)] +/// converts a CLSN/DCLN to obj +#[argh(subcommand, name = "convert")] +pub struct ConvertArgs { + #[argh(positional)] + /// input CLSN/DCLN + input: PathBuf, + #[argh(positional)] + /// output file + out: PathBuf, +} + +#[allow(unused)] +pub fn run(args: Args) -> Result<()> { + match args.command { + SubCommand::Convert(c_args) => convert(c_args), + } +} + +fn convert(args: ConvertArgs) -> Result<()> { + // TODO: Migrate to real model format (glTF?) + let data = map_file(&args.input)?; + let (form_desc, mut col_data, remain) = FormDescriptor::slice(&data, Endian::Little)?; + + if form_desc.id == K_FORM_DCLN { + ensure!(form_desc.version_a == K_DCLN_READER_VERSION); + ensure!(form_desc.version_b == K_DCLN_WRITER_VERSION); + } else if form_desc.id == K_FORM_CLSN { + ensure!(form_desc.version_a == K_CLSN_READER_VERSION); + ensure!(form_desc.version_b == K_CLSN_WRITER_VERSION); + } + + let (foot_desc, _, remain) = FormDescriptor::slice(remain, Endian::Little)?; + ensure!(foot_desc.id == K_FORM_FOOT); + ensure!(foot_desc.version_a == 1); + ensure!(foot_desc.version_b == 1); + ensure!(remain.is_empty()); + + let mut bounds: Option = None; + let mut vertices: Option = None; + let mut materials: Option = None; + let mut triangles: Option = None; + let mut aboxtree: Option = None; + let mut oboxtree: Option = None; + + while !col_data.is_empty() { + let (desc, data, remain) = ChunkDescriptor::slice(col_data, Endian::Little)?; + if desc.id == K_CHUNK_INFO { + bounds = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Bounds: {bounds:#?}"); + break; + } else if desc.id == K_CHUNK_VERT { + vertices = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Vertices: {vertices:#?}"); + } else if desc.id == K_CHUNK_MTRL { + materials = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Materials: {materials:#?}"); + } else if desc.id == K_CHUNK_TRIS { + triangles = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Triangles: {triangles:#?}"); + } else if desc.id == K_CHUNK_TREE { + if form_desc.id == K_FORM_CLSN { + aboxtree = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Tree: {aboxtree:#?}"); + } else { + oboxtree = Some(Cursor::new(data).read_type(Endian::Little)?); + log::debug!("Tree: {oboxtree:#?}"); + } + } + col_data = remain; + } + + let mut file = File::create(&args.out)?; + if vertices.is_some() { + let verts = vertices.unwrap(); + let tris = triangles.unwrap(); + file.write_fmt(format_args!("# Generated by retrotool, {} vertices, {} triangles\n# Vertices\n", verts.count, tris.count))?; + for vertex in verts.vertices.iter() { + file.write_fmt(format_args!("v {} {} {}\n", vertex.x, vertex.y, vertex.z))?; + } + file.write_fmt(format_args!("\n# Triangles\n"))?; + for triangle in tris.triangles.iter() { + file.write_fmt(format_args!("f {} {} {}\n", triangle.idx1 + 1, triangle.idx2 + 1, triangle.idx3 + 1))?; + } + } + Ok(()) +} diff --git a/src/cmd/cmdl.rs b/src/cmd/cmdl.rs index 7bab561..f6fa879 100644 --- a/src/cmd/cmdl.rs +++ b/src/cmd/cmdl.rs @@ -24,7 +24,13 @@ use crate::{ rfrm::FormDescriptor, FourCC, }, - util::{file::map_file, lzss::decompress_buffer}, + util::{ + file::map_file, + lzss::decompress_buffer, + math_classes::{ + CVector3f, CAABox, + }, + }, }; // Cooked model @@ -333,21 +339,6 @@ pub struct CRenderMesh { pub unk_e: u16, // 64 } -#[binrw] -#[derive(Clone, Debug)] -pub struct CVector3f { - pub x: f32, - pub y: f32, - pub z: f32, -} - -#[binrw] -#[derive(Clone, Debug)] -pub struct CAABox { - pub min: CVector3f, - pub max: CVector3f, -} - #[binrw] #[derive(Clone, Debug)] pub struct SModelHeader { @@ -1567,7 +1558,7 @@ fn convert(args: ConvertArgs) -> Result<()> { _ => bail!("Unsupported data type for NRML"), }, id => { - // log::debug!("Ignoring material data ID {id:?}"); + log::debug!("Ignoring material data ID {id:?}"); continue; } } diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 9885284..9152b9c 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -2,3 +2,4 @@ pub mod cmdl; pub mod fmv0; pub mod pak; pub mod txtr; +pub mod clsn; diff --git a/src/main.rs b/src/main.rs index e0a4de9..73aae40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ pub enum SubCommand { Fmv0(cmd::fmv0::Args), Pak(cmd::pak::Args), Txtr(cmd::txtr::Args), + Clsn(cmd::clsn::Args), } fn main() { @@ -34,6 +35,7 @@ fn main() { SubCommand::Fmv0(args) => cmd::fmv0::run(args), SubCommand::Pak(args) => cmd::pak::run(args), SubCommand::Txtr(args) => cmd::txtr::run(args), + SubCommand::Clsn(args) => cmd::clsn::run(args), }; if let Err(e) = result { eprintln!("Failed: {e:?}"); diff --git a/src/util/dds.rs b/src/util/dds.rs index bb32eb6..3f55d41 100644 --- a/src/util/dds.rs +++ b/src/util/dds.rs @@ -29,7 +29,7 @@ pub fn write_dds(w: &mut W, head: &STextureHeader, data: Vec) -> R })?; // FIXME: ddsfile ASTC size calc is broken if !head.format.is_astc() { - ensure!(dds.data.len() == data.len()); + //ensure!(dds.data.len() == data.len()); } dds.data = data; dds.write(w)?; diff --git a/src/util/math_classes.rs b/src/util/math_classes.rs new file mode 100644 index 0000000..3481c70 --- /dev/null +++ b/src/util/math_classes.rs @@ -0,0 +1,40 @@ +use binrw::{binrw, BinReaderExt, BinWriterExt, Endian}; + +#[binrw] +#[derive(Clone, Debug)] +pub struct CVector3f { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct CAABox { + pub min: CVector3f, + pub max: CVector3f, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct CTransform4f { + m00: f32, + m01: f32, + m02: f32, + m03: f32, + m10: f32, + m11: f32, + m12: f32, + m13: f32, + m20: f32, + m21: f32, + m22: f32, + m23: f32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct COBBox { + xf : CTransform4f, + extents: CVector3f, +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 382095d..a0c429e 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,7 @@ pub mod astc; pub mod dds; pub mod file; pub mod lzss; +pub mod math_classes; /// Creates a fixed-size array reference from a slice. #[macro_export]