Add preliminary CLSN/DCLN conversion

This commit is contained in:
Phillip Stephens
2023-02-17 23:19:04 -08:00
parent f1a527c491
commit bfa1c87d5c
7 changed files with 296 additions and 18 deletions
+243
View File
@@ -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<CVector3f>
}
#[binrw]
#[derive(Clone, Debug)]
pub struct Materials {
pub count: u32,
#[br(count = count)]
materials: Vec<CCollisionMaterial>
}
#[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<CIndexedTriangle>
}
#[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<AABoxTreeNode>,
}
#[binrw]
#[derive(Clone, Debug)]
pub struct OBBoxCollisionTree {
count: u32,
#[br(count = count)]
nodes: Vec<OBBoxTreeNode>,
}
#[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<CAABox> = None;
let mut vertices: Option<Vertices> = None;
let mut materials: Option<Materials> = None;
let mut triangles: Option<Triangles> = None;
let mut aboxtree: Option<AABoxCollisionTree> = None;
let mut oboxtree: Option<OBBoxCollisionTree> = 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(())
}
+8 -17
View File
@@ -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;
}
}
+1
View File
@@ -2,3 +2,4 @@ pub mod cmdl;
pub mod fmv0;
pub mod pak;
pub mod txtr;
pub mod clsn;
+2
View File
@@ -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:?}");
+1 -1
View File
@@ -29,7 +29,7 @@ pub fn write_dds<W: Write>(w: &mut W, head: &STextureHeader, data: Vec<u8>) -> 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)?;
+40
View File
@@ -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,
}
+1
View File
@@ -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]