diff --git a/Cargo.lock b/Cargo.lock index 12efce6..91284c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ [[package]] name = "cemconv" -version = "0.1.1" +version = "0.2.1" dependencies = [ "cem 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cgmath 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 5da84c6..bddb094 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cemconv" -version = "0.2.0" +version = "0.2.1" authors = ["coderbot16 "] description = "Tool for converting to and from Empire Earth's model format" license = "MIT" diff --git a/src/collada_export.rs b/src/collada_export.rs new file mode 100644 index 0000000..decdcee --- /dev/null +++ b/src/collada_export.rs @@ -0,0 +1,194 @@ +use cem::{ModelHeader, v2, V2, Scene, Model, Encode}; +use cgmath::{Point2, Point3, Vector3, Matrix4, Deg, InnerSpace}; +use std::fmt::{self, Write}; + +// TODO: Date and Time modified +pub const HEADER: &'static str = r#" + + + + cemconv user + cemconv 0.2.0 collada exporter + + 2018-01-01T00:00:00 + 2018-01-01T00:00:00 + + Z_UP + + + + + +"#; + +const FORMAT_POS: &'static str = r##""##; +const FORMAT_TEX: &'static str = r##""##; + +struct Geometry<'n> { + // Name + name: &'n str, + // Position (X, Y, Z) + mesh_positions: Vec, + // Normal (X, Y, Z) + mesh_normals: Vec, + // Texture (S, T) + mesh_map: Vec, + // Indices (V1, V2, V3) + polygons: Vec +} + +impl<'n> fmt::Display for Geometry<'n> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, r#" "#, self.name)?; + writeln!(f, r#" "#)?; + + let vertex_count = self.mesh_positions.len() / 3; + + { + let mut write_source = |source: &str, array: &[f32], stride: usize, format: &str| -> fmt::Result { + writeln!(f, r#" "#, self.name, source)?; + + writeln!(f, r#" "#, self.name, source, array.len())?; + for position in array { + write!(f, "{:.8} ", position)?; + } + writeln!(f, r#" "#)?; + writeln!(f, r##""##, self.name, source, vertex_count, stride)?; + writeln!(f, "{}", format)?; + writeln!(f, r##""##)?; + writeln!(f, r#" "#) + }; + + write_source("mesh-positions", &self.mesh_positions, 3, FORMAT_POS)?; + write_source("mesh-normals", &self.mesh_normals, 3, FORMAT_POS)?; + write_source("mesh-map", &self.mesh_map, 3, FORMAT_TEX)?; + } + + writeln!(f, r##" "##, self.name)?; + + writeln!(f, r#" "#, self.polygons.len() / 3)?; + writeln!(f, r##" "##, self.name)?; + writeln!(f, r##" "##, self.name)?; + writeln!(f, r##" "##, self.name)?; + + write!(f, r#"

"#)?; + for index in &self.polygons { + write!(f, "{0} {0} {0} ", index)?; + } + writeln!(f, r#"

"#)?; + writeln!(f, r#"
"#)?; + writeln!(f, r#"
"#)?; + write!(f, r#"
"#)?; + + + Ok(()) + } +} + +pub fn convert(cem: Scene) -> String { + let mut string = String::new(); + let model = cem.model; + let name = "scene_root"; + let triangle_data = &model.lod_levels[0]; + + string.push_str(HEADER); + + let mut polygons = vec![0; model.lod_levels[0].len() * 3]; + + for &v2::Material { ref name, texture, ref triangles, vertex_offset, vertex_count: _vertex_count, ref texture_name } in &model.materials { + let triangle_slice = triangles[0]; + + for index in 0..triangle_slice.len { + let index = index + triangle_slice.offset; + let triangle = &triangle_data[index as usize]; + + let indices = ( + vertex_offset + triangle.0, + vertex_offset + triangle.1, + vertex_offset + triangle.2 + ); + + polygons[(index as usize)*3 + 0] = indices.0; + polygons[(index as usize)*3 + 1] = indices.1; + polygons[(index as usize)*3 + 2] = indices.2; + } + } + + for (frame_index, frame) in model.frames.iter().enumerate() { + let mut geometry = Geometry { + name: &format!("{}_frame{}", name, frame_index), + mesh_positions: vec![0.0; frame.vertices.len() * 3], + mesh_normals: vec![0.0; frame.vertices.len() * 3], + mesh_map: vec![0.0; frame.vertices.len() * 2], + polygons: polygons.clone() + }; + + let transform = Matrix4::from_angle_x(Deg(-90.0)); + + for (index, vertex) in frame.vertices.iter().enumerate() { + let normal = (transform * vertex.normal.normalize().extend(0.0)).truncate(); + let position = Point3::from_homogeneous(transform * vertex.position.to_homogeneous()); + + geometry.mesh_positions[index*3 + 0] = position.x; + geometry.mesh_positions[index*3 + 1] = position.y; + geometry.mesh_positions[index*3 + 2] = position.z; + + geometry.mesh_normals[index*3 + 0] = normal.x; + geometry.mesh_normals[index*3 + 1] = normal.y; + geometry.mesh_normals[index*3 + 2] = normal.z; + + geometry.mesh_map[index*2 + 0] = vertex.texture.x; + geometry.mesh_map[index*2 + 1] = vertex.texture.y; + } + + writeln!(string, "{}", geometry).unwrap(); + } + + string.push_str("
\n"); + + /* + + + + + MODELNAME-mesh_morph_men_prophet_10_frame0 + + + + + + + + 0 + + + + + + + + + + + + + + */ + + string.push_str(r##" "##); + string.push('\n'); + + for frame_index in 0..model.frames.len() { + // Alternate transform: 1 0 0 {1} 0 1 0 0 0 0 1 0 0 0 0 1 + writeln!(string, r##"1 0 0 {1} 0 0 -1 0 0 1 0 0 0 0 0 1"##, format!("{}_frame{}", name, frame_index), frame_index).unwrap(); + } + + string.push_str(r##" "##); + string.push('\n'); + + string.push_str(r##" "##); + string.push('\n'); + string.push_str("
"); + + string +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index fc7fc81..d395719 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ extern crate structopt; extern crate structopt_derive; extern crate wavefront_obj; +mod collada_export; + use wavefront_obj::obj::{self, Object, Primitive, VTNIndex}; use std::fs::File; use std::collections::HashMap; @@ -28,7 +30,8 @@ struct Opt { enum Format { Cem { version: (u16, u16), frame_index: usize }, - Obj + Obj, + Collada } impl Format { @@ -41,6 +44,7 @@ impl Format { "cem" => Format::Cem { version: (2, 0), frame_index }, "ssmf" => Format::Cem { version: (2, 0), frame_index }, "obj" => Format::Obj, + "collada" => Format::Collada, _ => return None }) } @@ -163,6 +167,19 @@ fn convert(mut i: I, mut o: O, input_format: Format, format: Format) -> io unimplemented!("Cannon convert non-CEMv2 files to OBJ yet.") } }, + (Format::Cem { version: (_, _), frame_index }, Format::Collada) => { + let header = ModelHeader::read(&mut i)?; + + if header == V2::HEADER { + let scene = Scene::::read_without_header(&mut i)?; + + let buffer = collada_export::convert(scene); + + o.write_all(buffer.as_bytes()) + } else { + unimplemented!("Cannon convert non-CEMv2 files to COLLADA yet.") + } + }, _ => unimplemented!() } }