From 3266a27dcbc1ac992c01eb348822bd9c6cf52f45 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 5 Feb 2023 14:11:24 -0500 Subject: [PATCH 001/113] Fix duplicate append .mesh + .mat --- core/grim/src/model/gltf.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index 80ed52e8..6014283b 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -48,7 +48,7 @@ impl GLTFImporter { for scene in document.scenes() { // Create group name let group_name = match scene.name() { - Some(name) => format!("{}.grp", name), + Some(name) => format!("{}.grp", name.to_ascii_lowercase()), None => format!("group_{}.grp", scene.index()), }; @@ -90,7 +90,11 @@ impl GLTFImporter { for doc_mat in document.materials() { // Create mat name let mat_name = match doc_mat.name() { - Some(name) => format!("{}.mat", name), + Some(name) => match name.to_ascii_lowercase() { + // Append .mat if not already present + n if n.ends_with(".mat") => n, + n => format!("{n}.mat") + }, None => format!("mat_{}.mat", doc_mat.index().unwrap()), }; @@ -182,7 +186,11 @@ impl GLTFImporter { fn read_mesh(&mut self, mesh: &Mesh) -> Vec { let mesh_name_prefix = match mesh.name() { - Some(name) => name.to_string(), + Some(name) => match name.to_ascii_lowercase() { + // Remove .mesh ext if present (added back later) + n if n.ends_with(".mesh") => n[..(n.len() - 5)].to_string(), + n => n + }, None => format!("mesh_{}", mesh.index()), }; From 3f4da313dbdcc35f756d3146dddd06c5d188acc2 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 6 Feb 2023 00:21:26 -0500 Subject: [PATCH 002/113] Expose bitmap to python --- core/grim/src/io/file.rs | 19 ++++++ core/grim/src/lib.rs | 1 + core/grim/src/texture/bitmap.rs | 57 +++++++++++++++--- core/grim/src/texture/io.rs | 100 ++++++++++++++++++-------------- 4 files changed, 127 insertions(+), 50 deletions(-) diff --git a/core/grim/src/io/file.rs b/core/grim/src/io/file.rs index 0039d361..d536a2da 100644 --- a/core/grim/src/io/file.rs +++ b/core/grim/src/io/file.rs @@ -100,4 +100,23 @@ pub fn create_new_file>(file_path: T) -> std::io::Result { } File::create(file_path) +} + +pub fn create_missing_dirs>(file_path: T) -> std::io::Result<()> { + let file_path = file_path.as_ref(); + + let dir = if file_path.is_dir() { + Some(file_path) + } else { + file_path.parent() + }; + + // Create directory + if let Some(output_dir) = dir { + if !output_dir.exists() { + create_dir_all(&output_dir)?; + } + } + + Ok(()) } \ No newline at end of file diff --git a/core/grim/src/lib.rs b/core/grim/src/lib.rs index 8c27c9af..3c7172c2 100644 --- a/core/grim/src/lib.rs +++ b/core/grim/src/lib.rs @@ -22,6 +22,7 @@ fn grim(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } \ No newline at end of file diff --git a/core/grim/src/texture/bitmap.rs b/core/grim/src/texture/bitmap.rs index a83f0684..e3315c8b 100644 --- a/core/grim/src/texture/bitmap.rs +++ b/core/grim/src/texture/bitmap.rs @@ -1,14 +1,18 @@ +use crate::{Platform, SystemInfo}; +#[cfg(feature = "python")] use pyo3::prelude::*; + #[derive(Debug)] +#[cfg_attr(feature = "python", pyclass)] pub struct Bitmap { - pub bpp: u8, - pub encoding: u32, - pub mip_maps: u8, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub bpp: u8, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub encoding: u32, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub mip_maps: u8, - pub width: u16, - pub height: u16, - pub bpl: u16, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub width: u16, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub height: u16, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub bpl: u16, - pub raw_data: Vec, + #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub raw_data: Vec, } impl Bitmap { @@ -25,4 +29,43 @@ impl Bitmap { raw_data: Vec::new() } } +} + +#[cfg(feature = "python")] +#[pymethods] +impl Bitmap { + #[staticmethod] + fn from_file_path(path: &str) -> PyResult { + // TODO: Get from args + let sys_info = SystemInfo { + platform: Platform::PS3, + ..SystemInfo::default() + }; + + let image_input = super::Image::FromPath(path.to_owned()); + let bitmap = Bitmap::from_image(image_input, &sys_info); + + Ok(bitmap) + } + + fn save_to_file(&self, path: &str) -> PyResult<()> { + use crate::io::{BinaryStream, FileStream, Stream}; + use crate::scene::ObjectReadWrite; + use std::path::Path; + + // TODO: Get from args + let sys_info = SystemInfo { + platform: Platform::PS3, + ..SystemInfo::default() + }; + + // Ugh so much damn boilerplate... + let file_path = Path::new(path); + crate::io::create_missing_dirs(file_path).unwrap(); + + let mut file_stream = FileStream::from_path_as_read_write_create(file_path).unwrap(); + self.save(&mut file_stream, &sys_info).unwrap(); + + Ok(()) + } } \ No newline at end of file diff --git a/core/grim/src/texture/io.rs b/core/grim/src/texture/io.rs index 8d907626..184f9fd0 100644 --- a/core/grim/src/texture/io.rs +++ b/core/grim/src/texture/io.rs @@ -3,7 +3,7 @@ use crate::scene::ObjectReadWrite; use crate::texture::{Bitmap, decode_dx_image, decode_tpl_image, encode_dx_image, get_dx_bpp, DXGI_Encoding, TPLEncoding}; use crate::system::{Platform, SystemInfo}; use image::buffer::ConvertBuffer; -use image::{ImageBuffer, RgbaImage, ImageEncoder}; +use image::{ImageBuffer, ImageEncoder, ImageFormat, open, RgbaImage}; use rayon::prelude::*; use std::error::Error; @@ -40,49 +40,63 @@ pub enum Image<'a> { impl Bitmap { pub fn from_image(image: Image, info: &SystemInfo) -> Bitmap { - if let Image::FromRGBA { rgba, width, height, mips: _} = image { - match info.platform { - Platform::X360 | Platform::PS3 => { - let is_360 = info.platform.eq(&Platform::X360); - - // TODO: Support DXT1 - // Can't right now because underlying image library expects RGB slice instead of RGBA - let encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; - /*let mut encoding = DXGI_Encoding::DXGI_FORMAT_BC1_UNORM; - - // Use DXT5 encoding if alpha is used - if rgba.len() >= 4 && rgba.iter().skip(3).any(|&a| a < u8::MAX) { - encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; - }*/ - - let (bpp, dx_img_size, bpl) = match encoding { - DXGI_Encoding::DXGI_FORMAT_BC1_UNORM => (4, ((width as usize) * (height as usize)) / 2, width / 2), - _ => (8, (width as usize) * (height as usize), width) - }; - - let mut dx_img = vec![0u8; dx_img_size]; - - // Encode without mip maps for now - let rgba = &rgba[..(width as usize * height as usize * 4)]; - encode_dx_image(rgba, &mut dx_img, width as u32, encoding, is_360); - - return Bitmap { - bpp, - encoding: encoding as u32, - mip_maps: 0, - - width, - height, - bpl, - - raw_data: dx_img - } - }, - _ => todo!("Support other platforms") - } + // Decode rgba image data + let rgba_buff; // Hack to get around rust lifetimes... + let (rgba, width, height, _mips) = match image { + Image::FromRGBA { rgba, width, height, mips } => { + (rgba, width, height, mips) + }, + Image::FromPath(img_path) => { + let img = open(img_path).unwrap(); + + let width = img.width() as u16; + let height = img.height() as u16; + + rgba_buff = Some(img.into_rgba8().into_vec()); + (rgba_buff.as_ref().map(|b| b.as_slice()).unwrap(), width, height, 0) + }, + _ => todo!() + }; + + match info.platform { + Platform::X360 | Platform::PS3 => { + let is_360 = info.platform.eq(&Platform::X360); + + // TODO: Support DXT1 + // Can't right now because underlying image library expects RGB slice instead of RGBA + let encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; + /*let mut encoding = DXGI_Encoding::DXGI_FORMAT_BC1_UNORM; + + // Use DXT5 encoding if alpha is used + if rgba.len() >= 4 && rgba.iter().skip(3).any(|&a| a < u8::MAX) { + encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; + }*/ + + let (bpp, dx_img_size, bpl) = match encoding { + DXGI_Encoding::DXGI_FORMAT_BC1_UNORM => (4, ((width as usize) * (height as usize)) / 2, width / 2), + _ => (8, (width as usize) * (height as usize), width) + }; + + let mut dx_img = vec![0u8; dx_img_size]; + + // Encode without mip maps for now + let rgba = &rgba[..(width as usize * height as usize * 4)]; + encode_dx_image(rgba, &mut dx_img, width as u32, encoding, is_360); + + Bitmap { + bpp, + encoding: encoding as u32, + mip_maps: 0, + + width, + height, + bpl, + + raw_data: dx_img + } + }, + _ => todo!("Support other platforms") } - - todo!() } pub fn import_from_rgba(&mut self, rgba: &[u8]) { From bbcae63f045385c7e695249dd8a6749f28d92413 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 6 Feb 2023 00:35:23 -0500 Subject: [PATCH 003/113] Write external bitmaps as little endian --- core/grim/src/texture/bitmap.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/grim/src/texture/bitmap.rs b/core/grim/src/texture/bitmap.rs index e3315c8b..8ed72761 100644 --- a/core/grim/src/texture/bitmap.rs +++ b/core/grim/src/texture/bitmap.rs @@ -1,4 +1,5 @@ use crate::{Platform, SystemInfo}; +use crate::io::IOEndian; #[cfg(feature = "python")] use pyo3::prelude::*; #[derive(Debug)] @@ -56,6 +57,7 @@ impl Bitmap { // TODO: Get from args let sys_info = SystemInfo { platform: Platform::PS3, + endian: IOEndian::Little, ..SystemInfo::default() }; From 52428f5c01f6e94fa1ddd9bc777e01abb6e6046f Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 6 Feb 2023 17:48:02 -0500 Subject: [PATCH 004/113] Delete python example --- examples/read_ark.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 examples/read_ark.py diff --git a/examples/read_ark.py b/examples/read_ark.py deleted file mode 100644 index 192947d9..00000000 --- a/examples/read_ark.py +++ /dev/null @@ -1,19 +0,0 @@ -import grim -import sys - -def main(args: list[str]): - # Open ark - ark = grim.Ark.from_file_path(args[0]) - - # Print version + key info - print("Version:", ark.version) - print("Key:", hex(ark.encryption)) - - # Print entries - for entry in ark.entries: - print(entry.path) - - print("Finished!") - -if __name__ == '__main__': - main(sys.argv[1:]) \ No newline at end of file From 91748b73d90bdfe3af2410988c6a71749ac2d08e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 8 Feb 2023 23:38:01 -0500 Subject: [PATCH 005/113] Used DXT1 encoding if no alpha --- core/grim/src/texture/dxt.rs | 21 ++++++++++++++++++++- core/grim/src/texture/io.rs | 10 +++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/core/grim/src/texture/dxt.rs b/core/grim/src/texture/dxt.rs index 07f71aa5..4b0782ad 100644 --- a/core/grim/src/texture/dxt.rs +++ b/core/grim/src/texture/dxt.rs @@ -62,9 +62,28 @@ fn encode_dxt_with_lib(rgba: &[u8], dx_img: &mut [u8], width: u32, height: u32, _ => todo!("Implement other encodings") }; + // Hacky DXT1... + let mut dxt1_img = None; + if enc.eq(&DxtVariant::DXT1) { + // Convert RGBA to RGB + // Needs to be this arrangement for image encoder library + let rgb = rgba + .chunks(4) + .map(|p| [p[0], p[1], p[2]]) + .flatten() + .collect::>(); + + dxt1_img = Some(rgb); + } + + let image = dxt1_img + .as_ref() + .map(|dx| dx.as_slice()) + .unwrap_or(rgba); + // Encode dxt image let encoder = DxtEncoder::new(dx_img); - encoder.encode(rgba, width, height, enc).unwrap(); + encoder.encode(image, width, height, enc).unwrap(); } fn decode_dxt1_image(dx_img: &[u8], rgba: &mut [u8], width: u32, is_360: bool) { diff --git a/core/grim/src/texture/io.rs b/core/grim/src/texture/io.rs index 184f9fd0..168dbd70 100644 --- a/core/grim/src/texture/io.rs +++ b/core/grim/src/texture/io.rs @@ -61,16 +61,12 @@ impl Bitmap { match info.platform { Platform::X360 | Platform::PS3 => { let is_360 = info.platform.eq(&Platform::X360); - - // TODO: Support DXT1 - // Can't right now because underlying image library expects RGB slice instead of RGBA - let encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; - /*let mut encoding = DXGI_Encoding::DXGI_FORMAT_BC1_UNORM; + let mut encoding = DXGI_Encoding::DXGI_FORMAT_BC1_UNORM; // Use DXT5 encoding if alpha is used - if rgba.len() >= 4 && rgba.iter().skip(3).any(|&a| a < u8::MAX) { + if rgba.len() >= 4 && rgba.iter().skip(3).step_by(4).any(|&a| a < u8::MAX) { encoding = DXGI_Encoding::DXGI_FORMAT_BC3_UNORM; - }*/ + } let (bpp, dx_img_size, bpl) = match encoding { DXGI_Encoding::DXGI_FORMAT_BC1_UNORM => (4, ((width as usize) * (height as usize)) / 2, width / 2), From dcac6132fc4128cd5993ba8ed8b84b8cc2be6202 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 10 Feb 2023 22:13:23 -0500 Subject: [PATCH 006/113] Fix face rotation and material defaults --- core/grim/src/model/gltf.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index 6014283b..b4043861 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -8,7 +8,7 @@ use gltf::mesh::util::*; use gltf::json::extensions::scene::*; use gltf::json::extensions::mesh::*; use gltf::scene::Node; -use grim_traits::scene::{Color3, UV, Vector4}; +use grim_traits::scene::{Blend, Color3, UV, Vector4, ZMode}; use itertools::{Itertools, izip}; use nalgebra as na; use std::{borrow::Borrow, error::Error}; @@ -98,8 +98,12 @@ impl GLTFImporter { None => format!("mat_{}.mat", doc_mat.index().unwrap()), }; - let mut mat = MatObject::default(); - mat.name = mat_name; + let mut mat = MatObject { + name: mat_name, + blend: Blend::kBlendSrcAlpha, + z_mode: ZMode::kZModeNormal, + ..Default::default() + }; // Get base color let [r, g, b, a] = doc_mat.pbr_metallic_roughness().base_color_factor(); @@ -218,9 +222,9 @@ impl GLTFImporter { let faces: Vec<[u16; 3]> = faces_chunked .map(|f| [ - *f.get(2).unwrap(), // Clockwise -> Anti - *f.get(1).unwrap(), *f.get(0).unwrap(), + *f.get(1).unwrap(), + *f.get(2).unwrap(), ]) .collect(); From 44e7e973ef9bcc6443b5567e9c485d6c9023e347 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 10 Feb 2023 22:39:51 -0500 Subject: [PATCH 007/113] Rotate around z-axis for gltf import --- core/grim/src/model/gltf.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index b4043861..c37b2e69 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -312,11 +312,17 @@ impl GLTFImporter { } pub(crate) fn transform_verts(verts: &mut Vec) { + let rotate_on_z = na::Matrix4::from_axis_angle(&na::Vector3::z_axis(), std::f32::consts::PI); + for vert in verts.iter_mut() { let Vector4 { x, y, z, .. } = &mut vert.pos; // Update position let pos = super::MILOSPACE_TO_GLSPACE.transform_vector(&na::Vector3::new(*x, *y, *z)); + + // Rotate + let pos = rotate_on_z.transform_vector(&pos); + *x = *pos.get(0).unwrap(); *y = *pos.get(1).unwrap(); *z = *pos.get(2).unwrap(); From d5435fba1082368effa6079b3ed70aa66047e9fd Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 11 Feb 2023 20:43:16 -0500 Subject: [PATCH 008/113] Implement save trans anim --- core/grim/src/scene/trans_anim/io.rs | 45 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/core/grim/src/scene/trans_anim/io.rs b/core/grim/src/scene/trans_anim/io.rs index 8b49c3c0..24c9cd2c 100644 --- a/core/grim/src/scene/trans_anim/io.rs +++ b/core/grim/src/scene/trans_anim/io.rs @@ -101,9 +101,28 @@ impl ObjectReadWrite for TransAnim { save_object(self, &mut writer, info)?; save_anim(self, &mut writer, info, false)?; - todo!() + writer.write_prefixed_string(&self.trans_object)?; - //Ok(()) + // Write rot + trans keys + save_keys_quat(&self.rot_keys, &mut writer)?; + save_keys_vector3(&self.trans_keys, &mut writer)?; + + writer.write_prefixed_string(&self.trans_anim_owner)?; + writer.write_boolean(self.trans_spline)?; + writer.write_boolean(self.repeat_trans)?; + + // Write scale keys + save_keys_vector3(&self.scale_keys, &mut writer)?; + + writer.write_boolean(self.scale_spline)?; + writer.write_boolean(self.follow_path)?; + writer.write_boolean(self.rot_slerp)?; + + if version > 6 { + writer.write_boolean(self.rot_spline)?; + } + + Ok(()) } } @@ -143,4 +162,26 @@ fn load_keys_quat(reader: &mut Box) -> Result> } Ok(keys) +} + +fn save_keys_vector3(keys: &[AnimEvent], writer: &mut Box) -> Result<(), Box> { + writer.write_uint32(keys.len() as u32)?; + + for key in keys { + writer.write_float32(key.pos)?; + save_vector3(&key.value, writer)?; + } + + Ok(()) +} + +fn save_keys_quat(keys: &[AnimEvent], writer: &mut Box) -> Result<(), Box> { + writer.write_uint32(keys.len() as u32)?; + + for key in keys { + writer.write_float32(key.pos)?; + save_quat(&key.value, writer)?; + } + + Ok(()) } \ No newline at end of file From 44f130e05314acf42547eea03b7f6cd93a090331 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 11 Feb 2023 21:07:47 -0500 Subject: [PATCH 009/113] Add ability to track trans anims for gltf import --- core/grim/src/model/gltf.rs | 1 + core/grim/src/model/mod.rs | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index c37b2e69..f377936b 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -1,5 +1,6 @@ use crate::{SystemInfo, io::*}; use crate::model::{Draw, GroupObject, MatObject, MeshObject, TexPath, Trans, Vert}; +use crate::scene::TransAnim; use gltf::buffer::Data as BufferData; use gltf::{Document, Gltf, Mesh, Primitive, Scene}; use gltf::image::{Data as ImageData, Source}; diff --git a/core/grim/src/model/mod.rs b/core/grim/src/model/mod.rs index 409d14ed..1f0ccd0a 100644 --- a/core/grim/src/model/mod.rs +++ b/core/grim/src/model/mod.rs @@ -30,6 +30,7 @@ pub struct AssetManagager { meshes: Vec, materials: Vec, textures: Vec, + trans_anims: Vec, } impl AssetManagager { @@ -40,6 +41,7 @@ impl AssetManagager { meshes: Vec::new(), materials: Vec::new(), textures: Vec::new(), + trans_anims: Vec::new(), } } @@ -59,6 +61,10 @@ impl AssetManagager { self.textures.iter().find(|t| t.name.eq(name)) } + pub fn get_trans_anim(&self, name: &str) -> Option<&TransAnim> { + self.trans_anims.iter().find(|t| t.name.eq(name)) + } + pub fn add_group(&mut self, group: GroupObject) { self.groups.push(group); } @@ -75,6 +81,10 @@ impl AssetManagager { self.textures.push(tex); } + pub fn add_trans_anim(&mut self, trans_anim: TransAnim) { + self.trans_anims.push(trans_anim); + } + pub fn get_groups(&self) -> &Vec { &self.groups } @@ -86,8 +96,8 @@ impl AssetManagager { let groups = self.get_groups(); for grp in groups { - let meshes: Vec<&MeshObject> = (&grp.objects).iter().map(|m| self.get_mesh(m).unwrap()).collect(); - + // Iterate meshes + let meshes: Vec<&MeshObject> = (&grp.objects).iter().filter_map(|m| self.get_mesh(m)).collect(); for mesh in meshes { // Write mat let mat = self.get_material(&mesh.mat).unwrap(); @@ -111,6 +121,15 @@ impl AssetManagager { println!("Wrote {}", &mesh.name); } + // Iterate trans anims + let trans_anims: Vec<&TransAnim> = (&grp.objects).iter().filter_map(|m| self.get_trans_anim(m)).collect(); + for trans_anim in trans_anims { + // Write trans anim + let trans_anim_path = out_dir.as_ref().join(&trans_anim.name); + save_to_file(trans_anim, &trans_anim_path, &self.info)?; + println!("Wrote {}", &trans_anim.name); + } + // Write group let group_path = out_dir.as_ref().join(&grp.name); save_to_file(grp, &group_path, &self.info)?; From 1dbd178726b8ff8e6b30f2e6158a9ca35db322d8 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 09:58:56 -0500 Subject: [PATCH 010/113] Add scale decompose test --- core/grim/src/model/export.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 0b4a2cf8..03312bc3 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -2310,11 +2310,13 @@ mod tests { #[case([1.0, 2.0, 3.0])] #[case([-1.0, 2.0, -10.0])] fn decompose_trs_with_translation_test(#[case] input_trans: [f32; 3]) { + let [tx, ty, tz] = input_trans; + let mat = na::Matrix4::new( - 1.0, 0.0, 0.0, input_trans[0], - 0.0, 1.0, 0.0, input_trans[1], - 0.0, 0.0, 1.0, input_trans[2], - 0.0, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, tx, + 0.0, 1.0, 0.0, ty, + 0.0, 0.0, 1.0, tz, + 0.0, 0.0, 0.0, 1.0, ); let (trans, rotate, scale) = decompose_trs(mat); @@ -2323,4 +2325,26 @@ mod tests { assert_eq!(na::UnitQuaternion::identity(), rotate); assert_eq!(na::Vector3::new(1.0, 1.0, 1.0), scale); } + + #[rstest] + #[case([1.0 , 1.0, 1.0])] + #[case([20.0, 20.0, 20.0])] + #[case([50.0, 20.0, 50.0])] + #[case([10.0, 20.0, 30.0])] + fn decompose_trs_with_scale_test(#[case] input_scale: [f32; 3]) { + let [sx, sy, sz] = input_scale; + + let mat = na::Matrix4::new( + sx, 0.0, 0.0, 0.0, + 0.0, sy, 0.0, 0.0, + 0.0, 0.0, sz, 0.0, + 0.0, 0.0, 0.0, 1.0, + ); + + let (trans, rotate, scale) = decompose_trs(mat); + + assert_eq!(na::Vector3::zeros(), trans); + assert_eq!(na::UnitQuaternion::identity(), rotate); + assert_eq!(na::Vector3::new(sx, sy, sz), scale); + } } \ No newline at end of file From c28e31b4942c51c43ec5cf7ea18e284354eefe2d Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 15:16:24 -0500 Subject: [PATCH 011/113] Add rotz decompose tests --- core/grim/src/model/export.rs | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 03312bc3..6ac34c9b 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -2300,9 +2300,9 @@ mod tests { let (trans, rotate, scale) = decompose_trs(mat); - assert_eq!(na::Vector3::new(0.0, 0.0, 0.0), trans); + assert_eq!(na::Vector3::zeros(), trans); assert_eq!(na::UnitQuaternion::identity(), rotate); - assert_eq!(na::Vector3::new(1.0, 1.0, 1.0), scale); + assert_eq!(na::Vector3::from_element(1.0), scale); } #[rstest] @@ -2323,7 +2323,7 @@ mod tests { assert_eq!(na::Vector3::from(input_trans), trans); assert_eq!(na::UnitQuaternion::identity(), rotate); - assert_eq!(na::Vector3::new(1.0, 1.0, 1.0), scale); + assert_eq!(na::Vector3::from_element(1.0), scale); } #[rstest] @@ -2347,4 +2347,43 @@ mod tests { assert_eq!(na::UnitQuaternion::identity(), rotate); assert_eq!(na::Vector3::new(sx, sy, sz), scale); } + + #[rstest] + #[case(90.0, [0.0, 0.0, 0.7071068, 0.7071067])] // Both should be 0.7071068. Precision issue? + fn decompose_trs_rotz_test(#[case] input_deg: f32, #[case] expected_result: [f32; 4]) { + let rad = (input_deg * std::f32::consts::PI) / 180.0; + let [i, j, k, w] = expected_result; + + // Rotate on z-axis + let mat = na::Matrix4::from_axis_angle(&na::Vector3::z_axis(), rad); + + let (trans, rotate, scale) = decompose_trs(mat); + + assert_eq!(na::Vector3::zeros(), trans); + assert_eq!(na::UnitQuaternion::from_quaternion(na::Quaternion::new(w, i, j, k)), rotate); + assert_eq!(na::Vector3::from_element(1.0), scale); + } + + #[rstest] + #[case(90.0, [1.0 , 1.0, 1.0], [0.0, 0.0, 0.7071068, 0.7071067])] + #[case(90.0, [20.0, 20.0, 20.0], [0.0, 0.0, 0.7071068, 0.7071067])] + #[case(90.0, [50.0, 20.0, 50.0], [0.0, 0.0, 0.7071067, 0.7071068])] + #[case(90.0, [10.0, 20.0, 30.0], [0.0, 0.0, 0.7071067, 0.7071067])] + fn decompose_trs_rotz_with_scale_test(#[case] input_deg: f32, #[case] input_scale: [f32; 3], #[case] expected_result: [f32; 4]) { + let rad = (input_deg * std::f32::consts::PI) / 180.0; + let [sx, sy, sz] = input_scale; + let [i, j, k, w] = expected_result; + + let expected_scale = na::Vector3::new(sx, sy, sz); + + // Rotate on z-axis + scale + let mut mat = na::Matrix4::from_axis_angle(&na::Vector3::z_axis(), rad); + mat *= na::Matrix4::new_nonuniform_scaling(&expected_scale); + + let (trans, rotate, scale) = decompose_trs(mat); + + assert_eq!(na::Vector3::zeros(), trans); + assert_eq!(na::UnitQuaternion::from_quaternion(na::Quaternion::new(w, i, j, k)), rotate); + assert_eq!(expected_scale, scale); + } } \ No newline at end of file From 6f71a6af3c1200e198666e40efad978d6136157c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 17:47:16 -0500 Subject: [PATCH 012/113] Fix saving keys for trans anim --- core/grim/src/scene/trans_anim/io.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/grim/src/scene/trans_anim/io.rs b/core/grim/src/scene/trans_anim/io.rs index 24c9cd2c..011cbe78 100644 --- a/core/grim/src/scene/trans_anim/io.rs +++ b/core/grim/src/scene/trans_anim/io.rs @@ -168,8 +168,8 @@ fn save_keys_vector3(keys: &[AnimEvent], writer: &mut Box writer.write_uint32(keys.len() as u32)?; for key in keys { - writer.write_float32(key.pos)?; save_vector3(&key.value, writer)?; + writer.write_float32(key.pos)?; } Ok(()) @@ -179,8 +179,8 @@ fn save_keys_quat(keys: &[AnimEvent], writer: &mut Box) -> R writer.write_uint32(keys.len() as u32)?; for key in keys { - writer.write_float32(key.pos)?; save_quat(&key.value, writer)?; + writer.write_float32(key.pos)?; } Ok(()) From a7cdbc376e9939678d1dbd3088a58bd08db6ff45 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 18:22:56 -0500 Subject: [PATCH 013/113] Import trans anims from gltf --- core/grim/src/model/gltf.rs | 106 +++++++++++++++++++++++++++++++++++- core/grim/src/model/mod.rs | 25 +++------ 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index f377936b..c9460955 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -1,6 +1,7 @@ use crate::{SystemInfo, io::*}; use crate::model::{Draw, GroupObject, MatObject, MeshObject, TexPath, Trans, Vert}; -use crate::scene::TransAnim; +use crate::scene::{AnimEvent, TransAnim}; +use gltf::animation::util::ReadOutputs; use gltf::buffer::Data as BufferData; use gltf::{Document, Gltf, Mesh, Primitive, Scene}; use gltf::image::{Data as ImageData, Source}; @@ -9,10 +10,11 @@ use gltf::mesh::util::*; use gltf::json::extensions::scene::*; use gltf::json::extensions::mesh::*; use gltf::scene::Node; -use grim_traits::scene::{Blend, Color3, UV, Vector4, ZMode}; +use grim_traits::scene::{Blend, Color3, MiloObject, Quat, UV, Vector3, Vector4, ZMode}; use itertools::{Itertools, izip}; use nalgebra as na; -use std::{borrow::Borrow, error::Error}; +use std::collections::HashMap; +use std::error::Error; use std::path::{Path, PathBuf}; use crate::model::AssetManagager; @@ -23,6 +25,7 @@ pub struct GLTFImporter { buffers: Vec, images: Vec, mats: Vec, + node_names: HashMap, } impl GLTFImporter { @@ -35,6 +38,7 @@ impl GLTFImporter { buffers, images, mats: Vec::new(), + node_names: HashMap::new(), }) } @@ -78,6 +82,93 @@ impl GLTFImporter { asset_manager.add_group(group); } + // Process anims + for anim in document.animations() { + let name = anim // .tnm + .name() + .map(|n| n.to_owned()).unwrap_or_else(|| format!("anim_{}", anim.index())); + + // Group channels by target + let channels = anim.channels().collect::>(); + let group_channels = channels + .iter() + .fold(HashMap::new(), |mut acc, ch| { + let key = ch.target().node().index(); + + acc + .entry(key) + .and_modify(|e: &mut Vec<_>| e.push(ch)) + .or_insert_with(|| vec![ch]); + + acc + }); + + let mut anim_count = 0; + + for (node_idx, channels) in group_channels { + // Ignore if node doesn't have associated name + let Some(target_name) = self.node_names.get(&node_idx) else { + continue; + }; + + let anim_name = if anim_count == 0 { + format!("{name}.tnm") + } else { + format!("{name}_{anim_count}.tnm") + }; + anim_count += 1; + + //println!("Found {} anim channels for {}", channels.len(), target_name); + + let mut trans_anim = TransAnim { + name: anim_name.to_owned(), + trans_object: target_name.to_owned(), + trans_anim_owner: anim_name, + //trans_spline: true, + //repeat_trans: true, + //scale_spline: true, + //rot_slerp: true, + ..Default::default() + }; + + for channel in channels { + let reader = channel.reader(|buffer| Some(&self.buffers[buffer.index()])); + let inputs = reader.read_inputs().unwrap().collect::>(); + + match reader.read_outputs().unwrap() { + ReadOutputs::Translations(trans) => { + trans_anim.trans_keys = izip!(inputs.iter(), trans) + .map(|(t, [x, z, y])| AnimEvent { + pos: (*t * 30.) - 1.0, + value: Vector3 { x: x * 20., y: y * 20., z: z * 20. } + }) + .collect(); + }, + ReadOutputs::Rotations(rots) => { + trans_anim.rot_keys = izip!(inputs.iter(), rots.into_f32()) + .map(|(t, [x, z, y, w])| AnimEvent { + pos: (*t * 30.) - 1.0, + value: Quat { x, y, z, w } + }) + .collect(); + }, + ReadOutputs::Scales(scales) => { + trans_anim.scale_keys = izip!(inputs.iter(), scales) + .map(|(t, [x, z, y])| AnimEvent { + pos: (*t * 30.) - 1.0, + value: Vector3 { x: x * 20., y: y * 20., z: z * 20. } + }) + .collect(); + } + _ => continue + } + } + + // Add anim + asset_manager.add_trans_anim(trans_anim); + } + } + // Add materials to asset manager while !self.mats.is_empty() { asset_manager.add_material(self.mats.remove(0)); @@ -170,6 +261,15 @@ impl GLTFImporter { if let Some(mesh) = node.mesh() { let mut milo_meshes = self.read_mesh(&mesh); meshes.append(&mut milo_meshes); + + // Track mesh name for node + if !meshes.is_empty() && self.node_names.get(&node.index()).is_none() { + self.node_names + .insert( + node.index(), + meshes.first().map(|m| m.get_name().to_owned()).unwrap() + ); + } } // Process children diff --git a/core/grim/src/model/mod.rs b/core/grim/src/model/mod.rs index 1f0ccd0a..6fe1a504 100644 --- a/core/grim/src/model/mod.rs +++ b/core/grim/src/model/mod.rs @@ -85,17 +85,11 @@ impl AssetManagager { self.trans_anims.push(trans_anim); } - pub fn get_groups(&self) -> &Vec { - &self.groups - } - pub fn dump_to_directory(&self, out_dir: T) -> Result<(), Box> where T: AsRef { // Create output dir create_dir_if_not_exists(&out_dir)?; - let groups = self.get_groups(); - - for grp in groups { + for grp in self.groups.iter() { // Iterate meshes let meshes: Vec<&MeshObject> = (&grp.objects).iter().filter_map(|m| self.get_mesh(m)).collect(); for mesh in meshes { @@ -121,21 +115,20 @@ impl AssetManagager { println!("Wrote {}", &mesh.name); } - // Iterate trans anims - let trans_anims: Vec<&TransAnim> = (&grp.objects).iter().filter_map(|m| self.get_trans_anim(m)).collect(); - for trans_anim in trans_anims { - // Write trans anim - let trans_anim_path = out_dir.as_ref().join(&trans_anim.name); - save_to_file(trans_anim, &trans_anim_path, &self.info)?; - println!("Wrote {}", &trans_anim.name); - } - // Write group let group_path = out_dir.as_ref().join(&grp.name); save_to_file(grp, &group_path, &self.info)?; println!("Wrote {}", &grp.name); } + // Iterate anims + for trans_anim in self.trans_anims.iter() { + // Write trans anim + let trans_anim_path = out_dir.as_ref().join(&trans_anim.name); + save_to_file(trans_anim, &trans_anim_path, &self.info)?; + println!("Wrote {}", &trans_anim.name); + } + Ok(()) } } From c31e70982e82961beef271206ae0a8cd11692e84 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 21:51:14 -0500 Subject: [PATCH 014/113] Support meshes without textures for gltf import --- core/grim/src/model/gltf.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index c9460955..674c1103 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -333,7 +333,11 @@ impl GLTFImporter { reader.read_positions().unwrap(), reader.read_normals().unwrap(), //reader.read_colors(0).unwrap().into_rgb_f32().into_iter(), - reader.read_tex_coords(0).unwrap().into_f32(), + //reader.read_tex_coords(0).unwrap().into_f32(), + reader.read_tex_coords(0) // Hacky way to get tex coords or default if none found + .map(|tc| tc.into_f32() + .collect::>()) + .unwrap_or_default() ); let verts = verts_interleaved From c2fd84693c7b7a783e23e19e6bfee1ebad265f09 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 21:52:03 -0500 Subject: [PATCH 015/113] Remove hard-coded vertex scaling --- core/grim/src/model/gltf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index 674c1103..3cc9caa5 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -140,7 +140,7 @@ impl GLTFImporter { trans_anim.trans_keys = izip!(inputs.iter(), trans) .map(|(t, [x, z, y])| AnimEvent { pos: (*t * 30.) - 1.0, - value: Vector3 { x: x * 20., y: y * 20., z: z * 20. } + value: Vector3 { x, y, z } }) .collect(); }, @@ -156,7 +156,7 @@ impl GLTFImporter { trans_anim.scale_keys = izip!(inputs.iter(), scales) .map(|(t, [x, z, y])| AnimEvent { pos: (*t * 30.) - 1.0, - value: Vector3 { x: x * 20., y: y * 20., z: z * 20. } + value: Vector3 { x, y, z } }) .collect(); } From 4b0a9b694f329fa02900bb4a935fbe54f119efca Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 12 Feb 2023 22:08:21 -0500 Subject: [PATCH 016/113] Fallback on node name if mesh not found --- core/grim/src/model/gltf.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index 3cc9caa5..0e0d1411 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -82,6 +82,11 @@ impl GLTFImporter { asset_manager.add_group(group); } + let all_node_names = document + .nodes() + .map(|n| n.name()) + .collect::>(); + // Process anims for anim in document.animations() { let name = anim // .tnm @@ -107,7 +112,12 @@ impl GLTFImporter { for (node_idx, channels) in group_channels { // Ignore if node doesn't have associated name - let Some(target_name) = self.node_names.get(&node_idx) else { + /*let Some(target_name) = self.node_names.get(&node_idx) else { + continue; + };*/ + + // Fallback on actual node name if mesh not found + let Some(target_name) = self.node_names.get(&node_idx).map(|n| n.as_str()).or_else(|| *all_node_names.get(node_idx).unwrap()) else { continue; }; From de78c76dbb5c4f0271a9c1fe7401ef65c6abc738 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 13:58:45 -0500 Subject: [PATCH 017/113] Remove dead import --- core/grim/src/scene/trans_anim/io.rs | 1 - core/grim/src/scene/trans_anim/mod.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/scene/trans_anim/io.rs b/core/grim/src/scene/trans_anim/io.rs index 011cbe78..a3bb8e34 100644 --- a/core/grim/src/scene/trans_anim/io.rs +++ b/core/grim/src/scene/trans_anim/io.rs @@ -1,4 +1,3 @@ -use crate::dta::{DataArray, RootData, save_array}; use crate::io::{BinaryStream, SeekFrom, Stream}; use crate::scene::*; use crate::SystemInfo; diff --git a/core/grim/src/scene/trans_anim/mod.rs b/core/grim/src/scene/trans_anim/mod.rs index d291b175..ed98e7d3 100644 --- a/core/grim/src/scene/trans_anim/mod.rs +++ b/core/grim/src/scene/trans_anim/mod.rs @@ -6,6 +6,7 @@ use grim_traits::scene::*; pub use io::*; // TODO: Combine with keys used by prop anim +// Maybe rename to AnimKey... #[derive(Debug, Default)] pub struct AnimEvent { pub value: T, From 72c2e265a2258c9eb4461ce1efc1fc35101ec8ba Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 13:59:36 -0500 Subject: [PATCH 018/113] Implement load mesh anim --- core/grim/src/scene/mesh_anim/io.rs | 86 ++++++++++++++++++++++++++++ core/grim/src/scene/mesh_anim/mod.rs | 39 +++++++++++++ core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/object.rs | 4 ++ 4 files changed, 131 insertions(+) create mode 100644 core/grim/src/scene/mesh_anim/io.rs create mode 100644 core/grim/src/scene/mesh_anim/mod.rs diff --git a/core/grim/src/scene/mesh_anim/io.rs b/core/grim/src/scene/mesh_anim/io.rs new file mode 100644 index 00000000..03f5991e --- /dev/null +++ b/core/grim/src/scene/mesh_anim/io.rs @@ -0,0 +1,86 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use thiserror::Error as ThisError; +use std::error::Error; + +#[derive(Debug, ThisError)] +pub enum MeshAnimReadError { + #[error("MeshAnim version of {version} not supported")] + MeshAnimVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 1 => true, + _ => false + } +} + +impl ObjectReadWrite for MeshAnim { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + + // If not valid, return unsupported error + if !is_version_supported(version) { + // TODO: Switch to custom error + return Err(Box::new(MeshAnimReadError::MeshAnimVersionNotSupported { + version + })); + } + + if version >= 1 { + load_object(self, &mut reader, info)?; + } + load_anim(self, &mut reader, info, false)?; + + self.mesh = reader.read_prefixed_string()?; + + // Reset keys + self.vert_point_keys.clear(); + self.vert_text_keys.clear(); + self.vert_color_keys.clear(); + + self.vert_point_keys = load_keys(&mut reader, load_vector3)?; + self.vert_text_keys = load_keys(&mut reader, load_vector2)?; + self.vert_color_keys = load_keys(&mut reader, load_color4)?; + + self.keys_owner = reader.read_prefixed_string()?; + + Ok(()) + } + + fn save(&self, _stream: &mut dyn Stream, _info: &SystemInfo) -> Result<(), Box> { + todo!("Implement save() for MeshAnim") + + /*let mut writer = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 1; + Ok(())*/ + } +} + +fn load_keys(reader: &mut Box, loader: impl Fn(&mut T, &mut Box) -> Result<(), Box>) -> Result>, Box> { + let count = reader.read_uint32()?; + let mut keys = Vec::new(); + + for _ in 0..count { + let mut value = T::default(); + loader(&mut value, reader)?; + + let pos = reader.read_float32()?; + + keys.push(AnimEvent { + value, + pos + }) + } + + Ok(keys) +} \ No newline at end of file diff --git a/core/grim/src/scene/mesh_anim/mod.rs b/core/grim/src/scene/mesh_anim/mod.rs new file mode 100644 index 00000000..344a6697 --- /dev/null +++ b/core/grim/src/scene/mesh_anim/mod.rs @@ -0,0 +1,39 @@ +mod io; + +use super::{AnimEvent, Color4, Vector2, Vector3}; +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +// TODO: Probably add MeshAnim to derive macro +#[milo(Anim)] +pub struct MeshAnim { + pub mesh: String, + pub vert_point_keys: Vec>, + pub vert_text_keys: Vec>, + pub vert_color_keys: Vec>, + pub keys_owner: String, +} + +impl Default for MeshAnim { + fn default() -> MeshAnim { + MeshAnim { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // Anim object + anim_objects: Vec::new(), + frame: 0.0, + rate: AnimRate::default(), + + // MeshAnim object + mesh: String::default(), + vert_point_keys: Vec::new(), + vert_text_keys: Vec::new(), + vert_color_keys: Vec::new(), + keys_owner: String::default(), + } + } +} \ No newline at end of file diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index e9b45b2e..8d381379 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -9,6 +9,7 @@ mod group; mod io; mod mat; mod mesh; +mod mesh_anim; mod meta; mod milo; mod object_dir; @@ -34,6 +35,7 @@ pub use io::*; pub use self::mat::*; pub use self::meta::*; pub use self::mesh::*; +pub use self::mesh_anim::*; pub use self::milo::*; pub use self::object_dir::*; pub use self::object::*; diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index 0b58293e..6864d7cc 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -11,6 +11,7 @@ pub enum Object { Group(GroupObject), Mat(MatObject), Mesh(MeshObject), + MeshAnim(MeshAnim), P9SongPref(P9SongPref), PropAnim(PropAnim), SynthSample(SynthSample), @@ -38,6 +39,7 @@ impl Object { Object::Group(grp) => &grp.name, Object::Mat(mat) => &mat.name, Object::Mesh(mesh) => &mesh.name, + Object::MeshAnim(mesh_anim) => &mesh_anim.name, Object::P9SongPref(pref) => &pref.name, Object::PropAnim(prop) => &prop.name, Object::SynthSample(synth) => &synth.name, @@ -58,6 +60,7 @@ impl Object { Object::Group(_) => "Group", Object::Mat(_) => "Mat", Object::Mesh(_) => "Mesh", + Object::MeshAnim(_) => "MeshAnim", Object::P9SongPref(_) => "P9SongPref", Object::PropAnim(_) => "PropAnim", Object::SynthSample(_) => "SynthSample", @@ -123,6 +126,7 @@ impl Object { "Group" => unpack_object(packed, info).map(|o| Object::Group(o)), "Mat" => unpack_object(packed, info).map(|o| Object::Mat(o)), "Mesh" => unpack_object(packed, info).map(|o| Object::Mesh(o)), + "MeshAnim" => unpack_object(packed, info).map(|o| Object::MeshAnim(o)), "P9SongPref" => unpack_object(packed, info).map(|o| Object::P9SongPref(o)), "PropAnim" => unpack_object(packed, info).map(|o| Object::PropAnim(o)), "SynthSample" => unpack_object(packed, info).map(|o| Object::SynthSample(o)), From 62ab7677e7aee0cef37ead3d2ce1bad8b9455039 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 14:35:27 -0500 Subject: [PATCH 019/113] Fix loading mesh anim keys --- core/grim/src/scene/mesh_anim/io.rs | 15 +++++++++++---- core/grim/src/scene/mesh_anim/mod.rs | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/core/grim/src/scene/mesh_anim/io.rs b/core/grim/src/scene/mesh_anim/io.rs index 03f5991e..042127ca 100644 --- a/core/grim/src/scene/mesh_anim/io.rs +++ b/core/grim/src/scene/mesh_anim/io.rs @@ -66,18 +66,25 @@ impl ObjectReadWrite for MeshAnim { } } -fn load_keys(reader: &mut Box, loader: impl Fn(&mut T, &mut Box) -> Result<(), Box>) -> Result>, Box> { +fn load_keys(reader: &mut Box, loader: impl Fn(&mut T, &mut Box) -> Result<(), Box>) -> Result>>, Box> { let count = reader.read_uint32()?; let mut keys = Vec::new(); for _ in 0..count { - let mut value = T::default(); - loader(&mut value, reader)?; + let value_count = reader.read_uint32()?; + let mut values = Vec::new(); + + for _ in 0..value_count { + let mut value = T::default(); + loader(&mut value, reader)?; + + values.push(value); + } let pos = reader.read_float32()?; keys.push(AnimEvent { - value, + value: values, pos }) } diff --git a/core/grim/src/scene/mesh_anim/mod.rs b/core/grim/src/scene/mesh_anim/mod.rs index 344a6697..5adf0e6c 100644 --- a/core/grim/src/scene/mesh_anim/mod.rs +++ b/core/grim/src/scene/mesh_anim/mod.rs @@ -9,9 +9,9 @@ pub use io::*; #[milo(Anim)] pub struct MeshAnim { pub mesh: String, - pub vert_point_keys: Vec>, - pub vert_text_keys: Vec>, - pub vert_color_keys: Vec>, + pub vert_point_keys: Vec>>, + pub vert_text_keys: Vec>>, + pub vert_color_keys: Vec>>, pub keys_owner: String, } From 06470d9fe211377ab54f0d550f612a1a23e0b64a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 18:13:18 -0500 Subject: [PATCH 020/113] Derive clone for vector3 --- core/grim_traits/src/scene/common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim_traits/src/scene/common.rs b/core/grim_traits/src/scene/common.rs index 2e1e7b19..b4566546 100644 --- a/core/grim_traits/src/scene/common.rs +++ b/core/grim_traits/src/scene/common.rs @@ -68,7 +68,7 @@ pub struct Vector2 { pub y: f32, } -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct Vector3 { pub x: f32, pub y: f32, From 3be446660006a827ec3ada66d0098f4ed679b980 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 18:15:39 -0500 Subject: [PATCH 021/113] Add mesh anim viewer --- utils/anim_preview/Cargo.toml | 10 +++ utils/anim_preview/src/main.rs | 130 +++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 utils/anim_preview/Cargo.toml create mode 100644 utils/anim_preview/src/main.rs diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml new file mode 100644 index 00000000..0185b66c --- /dev/null +++ b/utils/anim_preview/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "anim_preview" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +grim = { workspace = true } +keyframe = "1.1.1" +rerun = "0.2.0" \ No newline at end of file diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs new file mode 100644 index 00000000..bc4adc95 --- /dev/null +++ b/utils/anim_preview/src/main.rs @@ -0,0 +1,130 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + +use std::env; +use std::error::Error; +use std::path::PathBuf; + +use keyframe::{CanTween, keyframes, Keyframe, AnimationSequence, functions::Linear, functions::EaseInOut}; + +use grim::{Platform, SystemInfo}; +use grim::io::*; +use grim::scene::{Anim, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Vector3}; + +use rerun::external::glam; +use rerun::{ + components::{ColorRGBA, Point3D, Radius}, + MsgSender, Session, + time::{Timeline} +}; + +#[derive(Clone, Default)] +struct Vec3Collection(Vec); + +impl CanTween for Vec3Collection { + fn ease(from: Self, to: Self, time: impl keyframe::num_traits::Float) -> Self { + let (Self(from), Self(to)) = (from, to); + + let mut points = Vec::new(); + + for (from, to) in from.into_iter().zip(to.into_iter()) { + let Vector3 { x: x1, y: y1, z: z1 } = from; + let Vector3 { x: x2, y: y2, z: z2 } = to; + + points.push(Vector3 { + x: f32::ease(x1, x2, time), + y: f32::ease(y1, y2, time), + z: f32::ease(z1, z2, time) + }); + } + + Self(points) + } +} + +fn main() -> Result<(), Box> { + let args: Vec<_> = env::args().skip(1).collect(); + + if args.len() < 1 { + println!("anim_preview.exe [input_milo_path]"); + return Ok(()); + } + + let milo_path = PathBuf::from(&args[0]); + + if let Some(file_name) = milo_path.file_name() { + let file_name = file_name.to_str().unwrap_or("file"); + + println!("Opening {}", file_name); + } + + // Open milo + let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let milo = MiloArchive::from_stream(&mut stream)?; + + // Unpack milo + let system_info = SystemInfo::guess_system_info(&milo, &milo_path); + let mut obj_dir = milo.unpack_directory(&system_info)?; + obj_dir.unpack_entries(&system_info)?; + + // Get mesh anims + let mesh_anims = obj_dir + .get_entries() + .iter() + .filter_map(|e| match e { + Object::MeshAnim(ma) => Some(ma), + _ => None + }) + .collect::>(); + + let mut session = Session::new(); + + for mesh_anim in mesh_anims { + println!("{}", mesh_anim.get_name()); + println!("{} point keys", mesh_anim.vert_point_keys.len()); + + // Collect mesh anim frames + let frames = mesh_anim + .vert_point_keys + .iter() + .map(|v| Keyframe::new(Vec3Collection(v.value.clone()), v.pos, Linear)) + .collect::>(); + + // Generate missing frames with linear interpolation + let mut sequence = AnimationSequence::from(frames); + sequence.advance_to(0.0); + + println!("Sequence length is {}", sequence.duration()); + + let mut interp_frames = Vec::new(); + + while !sequence.finished() { + let new_frame = sequence.now(); + interp_frames.push(new_frame); + + sequence.advance_by(1.0); + } + + println!("{} interpolated point keys", interp_frames.len()); + + for (i, frame) in interp_frames.into_iter().enumerate() { + let Vec3Collection(points) = frame; + + let glam_points = points + .into_iter() + .map(|Vector3 { x, y, z }| Point3D::new(x, y, z)) + .collect::>(); + + // Send points to rerun + MsgSender::new(mesh_anim.get_name().as_str()) + .with_component(&glam_points)? + .with_time(Timeline::new_sequence("frame"), i as i64) + .send(&mut session) + .unwrap(); + } + } + + session.show().unwrap(); + + Ok(()) +} From 28f6fef0f99840a1da052a51e087412cefd8b246 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 20:16:04 -0500 Subject: [PATCH 022/113] Add debug lines --- utils/anim_preview/src/main.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index bc4adc95..c0ca1779 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -13,9 +13,9 @@ use grim::scene::{Anim, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, V use rerun::external::glam; use rerun::{ - components::{ColorRGBA, Point3D, Radius}, + components::{ColorRGBA, LineStrip3D, MeshId, Point3D, Radius, RawMesh3D}, MsgSender, Session, - time::{Timeline} + time::Timeline }; #[derive(Clone, Default)] @@ -110,6 +110,20 @@ fn main() -> Result<(), Box> { for (i, frame) in interp_frames.into_iter().enumerate() { let Vec3Collection(points) = frame; + /*let strip: LineStrip3D = points + .iter() + .map(|Vector3 { x, y, z }| [ *x, *y, *z ]) + .collect::>() + .into();*/ + + /*let mesh = RawMesh3D { + mesh_id: MeshId::random(), + positions: points + .iter() + .map(|Vector3 { x, y, z }| [ *x, *y, *z ]) + .collect::>() + };*/ + let glam_points = points .into_iter() .map(|Vector3 { x, y, z }| Point3D::new(x, y, z)) @@ -121,6 +135,13 @@ fn main() -> Result<(), Box> { .with_time(Timeline::new_sequence("frame"), i as i64) .send(&mut session) .unwrap(); + + // Send line strip to rerun + /*MsgSender::new(mesh_anim.get_name().as_str()) + .with_component(&[strip])? + .with_time(Timeline::new_sequence("frame"), i as i64) + .send(&mut session) + .unwrap();*/ } } From 35c0ceb38b270939591b33d7947ca99228b41f25 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 22:28:14 -0500 Subject: [PATCH 023/113] Parse skeleton for anim preview --- utils/anim_preview/Cargo.toml | 1 + utils/anim_preview/src/main.rs | 139 ++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index 0185b66c..d1f55551 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -7,4 +7,5 @@ edition.workspace = true [dependencies] grim = { workspace = true } keyframe = "1.1.1" +nalgebra = "0.32.1" rerun = "0.2.0" \ No newline at end of file diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index c0ca1779..cf7013bb 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -3,13 +3,16 @@ use std::env; use std::error::Error; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use keyframe::{CanTween, keyframes, Keyframe, AnimationSequence, functions::Linear, functions::EaseInOut}; use grim::{Platform, SystemInfo}; use grim::io::*; -use grim::scene::{Anim, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Vector3}; +use grim::scene::{Anim, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Trans, Vector3}; + +use nalgebra as na; use rerun::external::glam; use rerun::{ @@ -145,7 +148,141 @@ fn main() -> Result<(), Box> { } } + // Get bones + let root_bone = BoneNode::new(&obj_dir); + + if let Some(root_bone) = root_bone { + let points = generate_bone_points(&root_bone); + + MsgSender::new(root_bone.name) + .with_component(&points)? + .send(&mut session) + .unwrap(); + } + session.show().unwrap(); Ok(()) } + +fn generate_bone_points(bone: &BoneNode) -> Vec { + let mut points = Vec::new(); + + //let v: na::Vector3 = bone.transform.transform_vector(&na::Vector3::zeros()); + let v = bone.transform.column(3).xyz(); + points.push(Point3D::from([v[0], v[1], v[2]])); + + for child in bone.children.iter() { + let mut child_points = generate_bone_points(child); + points.append(&mut child_points); + } + + points +} + +pub struct BoneNode<'a> { + pub name: &'a str, + pub object: Option<&'a dyn Trans>, + pub children: Vec>, + pub transform: na::Matrix4, +} + +impl<'a> BoneNode<'a> { + fn new(obj_dir: &'a ObjectDir) -> Option { + let dir_name = match obj_dir { + ObjectDir::ObjectDir(base) => base.name.as_str(), + }; + + // Get bones + let bones = obj_dir + .get_entries() + .iter() + .filter_map(|o| match o { + Object::Mesh(m) if m.faces.is_empty() // GH1 bones + => Some(m as &dyn Trans), + Object::Trans(t) => Some(t as &dyn Trans), + _ => None + }) + .map(|b| (b.get_name().as_str(), b)) + .collect::>(); + + // Map children + let child_map = bones + .iter() + .fold(HashMap::new(), |mut acc: HashMap<&str, Vec<&'a dyn Trans>>, (_, b)| { + if b.get_parent().eq(b.get_name()) { + // If bone references self, ignore + return acc; + } + + acc + .entry(b.get_parent().as_str()) + .and_modify(|e| e.push(*b)) + .or_insert(vec![*b]); + + acc + }); + + // Create root node + let mut root = BoneNode { + name: dir_name, + object: None, + children: Vec::new(), + transform: na::Matrix4::identity(), + }; + + // Find bones that belong to object dir + root.children = root.find_child_nodes(&bones, &child_map); + + if root.children.is_empty() { + return None; + } + + Some(root) + } + + fn find_child_nodes(&self, bone_map: &HashMap<&str, &'a dyn Trans>, child_map: &HashMap<&str, Vec<&'a dyn Trans>>) -> Vec> { + let parent_name = self.name; + + let Some(children) = child_map.get(parent_name) else { + return Vec::new(); + }; + + children + .iter() + .map(|c| { + let trans_obj = bone_map.get(c.get_name().as_str()).map(|o| *o); + let local_transform = trans_obj + .map(|o| { + let m = o.get_local_xfm(); + + na::Matrix4::new( + // Column-major order... + m.m11, m.m21, m.m31, m.m41, + m.m12, m.m22, m.m32, m.m42, + m.m13, m.m23, m.m33, m.m43, + m.m14, m.m24, m.m34, m.m44 + ) + + /*na::Matrix4::new( + m.m11, m.m12, m.m13, m.m14, + m.m21, m.m22, m.m23, m.m24, + m.m31, m.m32, m.m33, m.m34, + m.m41, m.m42, m.m43, m.m44 + )*/ + }) + .unwrap_or(na::Matrix4::identity()); + + let mut bone = BoneNode { + name: c.get_name().as_str(), + object: trans_obj, + children: Vec::new(), + transform: self.transform * local_transform + }; + + bone.children = bone.find_child_nodes(bone_map, child_map); + bone + }) + .collect() + } +} \ No newline at end of file From f6c9d7036cf75f710142eeda0389d218a9ebbb26 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 19 Feb 2023 22:47:26 -0500 Subject: [PATCH 024/113] Generate lines for bone hierarchy --- utils/anim_preview/src/main.rs | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index cf7013bb..840ea2b9 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -152,12 +152,17 @@ fn main() -> Result<(), Box> { let root_bone = BoneNode::new(&obj_dir); if let Some(root_bone) = root_bone { - let points = generate_bone_points(&root_bone); + let (points, lines) = generate_bone_points(&root_bone); MsgSender::new(root_bone.name) - .with_component(&points)? - .send(&mut session) - .unwrap(); + .with_component(&points)? + .send(&mut session) + .unwrap(); + + MsgSender::new(format!("{}_lines", root_bone.name)) + .with_component(&lines)? + .send(&mut session) + .unwrap(); } session.show().unwrap(); @@ -165,19 +170,34 @@ fn main() -> Result<(), Box> { Ok(()) } -fn generate_bone_points(bone: &BoneNode) -> Vec { +fn generate_bone_points(bone: &BoneNode) -> (Vec, Vec) { let mut points = Vec::new(); + let mut lines = Vec::new(); //let v: na::Vector3 = bone.transform.transform_vector(&na::Vector3::zeros()); let v = bone.transform.column(3).xyz(); points.push(Point3D::from([v[0], v[1], v[2]])); + // Generate line strips + let mut strips = bone + .children + .iter() + .map(|c| { + let cv = c.transform.column(3).xyz(); + vec![[v[0], v[1], v[2]], [cv.x, cv.y, cv.z]].into() + }) + .collect::>(); + + lines.append(&mut strips); + for child in bone.children.iter() { - let mut child_points = generate_bone_points(child); + let (mut child_points, mut child_lines) = generate_bone_points(child); + points.append(&mut child_points); + lines.append(&mut child_lines); } - points + (points, lines) } pub struct BoneNode<'a> { From 35963805c2dd113740a5e2272167619be55e50ac Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 20 Feb 2023 14:01:41 -0500 Subject: [PATCH 025/113] Make decode_samples public --- core/grim/src/scene/char_bones_samples/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/scene/char_bones_samples/mod.rs b/core/grim/src/scene/char_bones_samples/mod.rs index 6e78db10..04a789d0 100644 --- a/core/grim/src/scene/char_bones_samples/mod.rs +++ b/core/grim/src/scene/char_bones_samples/mod.rs @@ -120,7 +120,7 @@ impl CharBonesSamples { self.computed_flags = (self.computed_sizes.last().unwrap() + 0xF) & 0xFFFF_FFF0; } - pub(crate) fn decode_samples(&self, sys_info: &SystemInfo) -> Vec { + pub fn decode_samples(&self, sys_info: &SystemInfo) -> Vec { let EncodedSamples::Compressed(bones, compressed_samples) = &self.samples else { // Maybe throw error? return Vec::new(); From 86b3a9c05705dd89c72538a11bcce4d8d2bd1c76 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 20 Feb 2023 14:02:04 -0500 Subject: [PATCH 026/113] Parse char clip samples for anim preview --- utils/anim_preview/src/main.rs | 227 +++++++++++++++++++++++++++++---- 1 file changed, 202 insertions(+), 25 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index 840ea2b9..74284608 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -10,7 +10,7 @@ use keyframe::{CanTween, keyframes, Keyframe, AnimationSequence, functions::Line use grim::{Platform, SystemInfo}; use grim::io::*; -use grim::scene::{Anim, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Trans, Vector3}; +use grim::scene::{Anim, CharBoneSample, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Trans, Vector3}; use nalgebra as na; @@ -45,6 +45,31 @@ impl CanTween for Vec3Collection { } } +struct MiloLoader { + pub path: PathBuf, + pub sys_info: SystemInfo, + pub obj_dir: ObjectDir, +} + +impl MiloLoader { + pub fn from_path(milo_path: PathBuf) -> Result> { + // Open milo + let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let milo = MiloArchive::from_stream(&mut stream)?; + + // Unpack milo + let system_info = SystemInfo::guess_system_info(&milo, &milo_path); + let mut obj_dir = milo.unpack_directory(&system_info)?; + obj_dir.unpack_entries(&system_info)?; + + Ok(Self { + path: milo_path, + sys_info: system_info, + obj_dir + }) + } +} + fn main() -> Result<(), Box> { let args: Vec<_> = env::args().skip(1).collect(); @@ -62,16 +87,11 @@ fn main() -> Result<(), Box> { } // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); - let milo = MiloArchive::from_stream(&mut stream)?; - - // Unpack milo - let system_info = SystemInfo::guess_system_info(&milo, &milo_path); - let mut obj_dir = milo.unpack_directory(&system_info)?; - obj_dir.unpack_entries(&system_info)?; + let base_milo_loader = MiloLoader::from_path(milo_path)?; // Get mesh anims - let mesh_anims = obj_dir + let mesh_anims = base_milo_loader + .obj_dir .get_entries() .iter() .filter_map(|e| match e { @@ -149,20 +169,92 @@ fn main() -> Result<(), Box> { } // Get bones - let root_bone = BoneNode::new(&obj_dir); + let root_bone = BoneNode::new(&base_milo_loader.obj_dir); + + if let Some(mut root_bone) = root_bone { + let anim_milo_loader_result = args + .get(1) + .map(|p| PathBuf::from(&p)) + .and_then(|p| MiloLoader::from_path(p).ok()); + + if let Some(anim_milo_loader) = anim_milo_loader_result { + let info = &anim_milo_loader.sys_info; + let anims = anim_milo_loader + .obj_dir + .get_entries() + .iter() + .filter_map(|o| match o { + Object::CharClipSamples(ccs) => Some(ccs), + _ => None, + }) + .collect::>(); + + let char_clip = args + .get(2) + .and_then(|anim_name| anims + .iter() + .find(|a| a + .get_name() + .eq(anim_name))) + .unwrap_or_else(|| anims.first().unwrap()); // Default to first one if not found (or_else didn't work...) + let default_frames = vec![0.0]; + + println!("Char clip: {}", char_clip.get_name()); - if let Some(root_bone) = root_bone { - let (points, lines) = generate_bone_points(&root_bone); + let bone_samples = [&char_clip.full, &char_clip.one] + .iter() + .flat_map(|cbs| cbs + .decode_samples(info) + .into_iter() + .map(|s| (s, if !cbs.frames.is_empty() { &cbs.frames } else { &default_frames }))) + .collect::>(); + + let sample_count = bone_samples + .iter() + .map(|(cbs, _)| 0 + .max(cbs.pos.as_ref().map(|(_, p)| p.len()).unwrap_or_default()) + .max(cbs.quat.as_ref().map(|(_, q)| q.len()).unwrap_or_default()) + .max(cbs.rotz.as_ref().map(|(_, r)| r.len()).unwrap_or_default()) + ) + .max() + .unwrap_or_default(); + + let bone_sample_map = bone_samples + .iter() + .map(|(cbs, frames)| (cbs.symbol.as_str(), (cbs, *frames))) + .collect::>(); + + println!("Found {sample_count} samples for {} bones", bone_samples.len()); - MsgSender::new(root_bone.name) - .with_component(&points)? - .send(&mut session) - .unwrap(); + /*for (bone, _) in bone_samples.iter() { + println!("{}", &bone.symbol); + }*/ - MsgSender::new(format!("{}_lines", root_bone.name)) - .with_component(&lines)? - .send(&mut session) - .unwrap(); + + for i in 0..sample_count { + // If sample not found, use last one? + // TODO: Iterpolate from frames + + root_bone.recompute_world_transform(na::Matrix4::identity(), &bone_sample_map, i); + add_bones_to_session(&root_bone, &mut session, i); + } + + /*for (char_bone_sample, frames) in bone_samples { + //char_bone_sample. + }*/ + } else { + let (points, lines) = generate_bone_points(&root_bone); + + MsgSender::new(root_bone.name) + .with_component(&points)? + .send(&mut session) + .unwrap(); + + MsgSender::new(format!("{}_lines", root_bone.name)) + .with_component(&lines)? + .send(&mut session) + .unwrap(); + } } session.show().unwrap(); @@ -175,7 +267,7 @@ fn generate_bone_points(bone: &BoneNode) -> (Vec, Vec) { let mut lines = Vec::new(); //let v: na::Vector3 = bone.transform.transform_vector(&na::Vector3::zeros()); - let v = bone.transform.column(3).xyz(); + let v = bone.world_bind_transform.column(3).xyz(); points.push(Point3D::from([v[0], v[1], v[2]])); // Generate line strips @@ -183,7 +275,7 @@ fn generate_bone_points(bone: &BoneNode) -> (Vec, Vec) { .children .iter() .map(|c| { - let cv = c.transform.column(3).xyz(); + let cv = c.world_bind_transform.column(3).xyz(); vec![[v[0], v[1], v[2]], [cv.x, cv.y, cv.z]].into() }) .collect::>(); @@ -204,7 +296,8 @@ pub struct BoneNode<'a> { pub name: &'a str, pub object: Option<&'a dyn Trans>, pub children: Vec>, - pub transform: na::Matrix4, + pub local_bind_transform: na::Matrix4, + pub world_bind_transform: na::Matrix4, } impl<'a> BoneNode<'a> { @@ -248,7 +341,8 @@ impl<'a> BoneNode<'a> { name: dir_name, object: None, children: Vec::new(), - transform: na::Matrix4::identity(), + local_bind_transform: na::Matrix4::identity(), + world_bind_transform: na::Matrix4::identity(), }; // Find bones that belong to object dir @@ -297,7 +391,8 @@ impl<'a> BoneNode<'a> { name: c.get_name().as_str(), object: trans_obj, children: Vec::new(), - transform: self.transform * local_transform + local_bind_transform: local_transform, + world_bind_transform: self.world_bind_transform * local_transform }; bone.children = bone.find_child_nodes(bone_map, child_map); @@ -305,4 +400,86 @@ impl<'a> BoneNode<'a> { }) .collect() } + + fn recompute_world_transform(&mut self, parent_transform: na::Matrix4, bone_sample_map: &HashMap<&str, (&CharBoneSample, &Vec)>, i: usize) { + if let Some((sample, _)) = bone_sample_map.get(self.name) { + // TODO: Multiple by bone weight? + let pos = sample + .pos + .as_ref() + .and_then(|(_, p)| p.get(i).or_else(|| p.last())) + .map(|v| na::Vector3::new(v.x, v.y, v.z)) + .unwrap_or_default(); + + let quat = sample + .quat + .as_ref() + .and_then(|(_, q)| q.get(i).or_else(|| q.last())) + .map(|q| na::UnitQuaternion::from_quaternion( + na::Quaternion::new(q.w, q.x, q.y, q.z) + )) + .unwrap_or(na::UnitQuaternion::identity()); + + let rotz = sample + .rotz + .as_ref() + .and_then(|(_, r)| r.get(i).or_else(|| r.last())) + .map(|z| na::UnitQuaternion::from_axis_angle( + &na::Vector3::z_axis(), + std::f32::consts::PI * z + )) + .unwrap_or(na::UnitQuaternion::identity()); + + let anim_transform = na::Matrix4::identity() + .append_translation(&pos) * + quat.to_homogeneous() * + rotz.to_homogeneous(); + + //let applied_transform = self.local_bind_transform * anim_transform; + self.world_bind_transform = parent_transform * self.local_bind_transform * anim_transform; + } else { + self.world_bind_transform = parent_transform * self.local_bind_transform; + } + + for ch in self.children.iter_mut() { + ch.recompute_world_transform(self.world_bind_transform, bone_sample_map, i); + } + } + + fn get_world_pos(&self) -> na::Vector3 { + self.world_bind_transform.column(3).xyz() + } +} + +fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { + let v = bone.get_world_pos(); + let points = vec![Point3D::from([v[0], v[1], v[2]])]; + + // Generate line strips + let strips = bone + .children + .iter() + .map(|c| { + let cv = c.get_world_pos(); + vec![[v[0], v[1], v[2]], [cv.x, cv.y, cv.z]].into() + }) + .collect::>(); + + MsgSender::new(bone.name) + .with_component(&points) + .unwrap() + .with_time(Timeline::new_sequence("frame"), i as i64) + .send(session) + .unwrap(); + + MsgSender::new(format!("{}_lines", bone.name)) + .with_component(&strips) + .unwrap() + .with_time(Timeline::new_sequence("frame"), i as i64) + .send(session) + .unwrap(); + + for ch in bone.children.iter() { + add_bones_to_session(ch, session, i); + } } \ No newline at end of file From b2d54835313cce0904df5583e92f34677561d699 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 20 Feb 2023 21:42:44 -0500 Subject: [PATCH 027/113] Group lines + arrows under mesh component --- utils/anim_preview/src/main.rs | 132 +++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 16 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index 74284608..a78c41c6 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -16,7 +16,8 @@ use nalgebra as na; use rerun::external::glam; use rerun::{ - components::{ColorRGBA, LineStrip3D, MeshId, Point3D, Radius, RawMesh3D}, + coordinates::{Handedness, SignedAxis3}, + components::{Arrow3D, ColorRGBA, LineStrip3D, MeshId, Point3D, Quaternion, Radius, RawMesh3D, Rigid3, Scalar, TextEntry, Transform, Vec3D, ViewCoordinates}, MsgSender, Session, time::Timeline }; @@ -102,6 +103,34 @@ fn main() -> Result<(), Box> { let mut session = Session::new(); + MsgSender::new(format!("world")) + /*.with_component(&[ + ViewCoordinates::from_up_and_handedness( + SignedAxis3::POSITIVE_Z, + Handedness::Right) + ]) + .unwrap()*/ + .with_splat(ViewCoordinates::from_up_and_handedness( + SignedAxis3::POSITIVE_Z, + Handedness::Right)) + .unwrap() + .with_splat(Transform::Rigid3({ + let q = na::UnitQuaternion + ::from_axis_angle( + &na::Vector3::z_axis(), + std::f32::consts::PI + ); + + Rigid3 { + rotation: Quaternion::new(q.i, q.j, q.k, q.w), + ..Default::default() + } + })) + .unwrap() + .with_timeless(true) + .send(&mut session) + .unwrap(); + for mesh_anim in mesh_anims { println!("{}", mesh_anim.get_name()); println!("{} point keys", mesh_anim.vert_point_keys.len()); @@ -172,6 +201,12 @@ fn main() -> Result<(), Box> { let root_bone = BoneNode::new(&base_milo_loader.obj_dir); if let Some(mut root_bone) = root_bone { + /*root_bone = root_bone + .children + .into_iter() + .find(|b| b.name.eq("bone_pelvis.mesh")) + .unwrap();*/ + let anim_milo_loader_result = args .get(1) .map(|p| PathBuf::from(&p)) @@ -235,7 +270,7 @@ fn main() -> Result<(), Box> { // If sample not found, use last one? // TODO: Iterpolate from frames - root_bone.recompute_world_transform(na::Matrix4::identity(), &bone_sample_map, i); + root_bone.recompute_world_anim_transform(na::Matrix4::identity(), &bone_sample_map, i); add_bones_to_session(&root_bone, &mut session, i); } @@ -298,6 +333,7 @@ pub struct BoneNode<'a> { pub children: Vec>, pub local_bind_transform: na::Matrix4, pub world_bind_transform: na::Matrix4, + pub world_anim_transform: na::Matrix4, } impl<'a> BoneNode<'a> { @@ -343,6 +379,7 @@ impl<'a> BoneNode<'a> { children: Vec::new(), local_bind_transform: na::Matrix4::identity(), world_bind_transform: na::Matrix4::identity(), + world_anim_transform: na::Matrix4::identity(), }; // Find bones that belong to object dir @@ -392,7 +429,8 @@ impl<'a> BoneNode<'a> { object: trans_obj, children: Vec::new(), local_bind_transform: local_transform, - world_bind_transform: self.world_bind_transform * local_transform + world_bind_transform: self.world_bind_transform * local_transform, + world_anim_transform: self.world_bind_transform * local_transform, }; bone.children = bone.find_child_nodes(bone_map, child_map); @@ -401,7 +439,7 @@ impl<'a> BoneNode<'a> { .collect() } - fn recompute_world_transform(&mut self, parent_transform: na::Matrix4, bone_sample_map: &HashMap<&str, (&CharBoneSample, &Vec)>, i: usize) { + fn recompute_world_anim_transform(&mut self, parent_transform: na::Matrix4, bone_sample_map: &HashMap<&str, (&CharBoneSample, &Vec)>, i: usize) { if let Some((sample, _)) = bone_sample_map.get(self.name) { // TODO: Multiple by bone weight? let pos = sample @@ -416,7 +454,12 @@ impl<'a> BoneNode<'a> { .as_ref() .and_then(|(_, q)| q.get(i).or_else(|| q.last())) .map(|q| na::UnitQuaternion::from_quaternion( - na::Quaternion::new(q.w, q.x, q.y, q.z) + na::Quaternion::new( + q.w, + q.x, + q.y, + q.z + ) )) .unwrap_or(na::UnitQuaternion::identity()); @@ -430,51 +473,108 @@ impl<'a> BoneNode<'a> { )) .unwrap_or(na::UnitQuaternion::identity()); + /*let rotz2 = sample + .rotz + .as_ref() + .and_then(|(_, r)| r.get(i).or_else(|| r.last())) + .map(|z| na::Rotation3::from_axis_angle(&na::Vector3::z_axis(), *z).to_homogeneous()) + .unwrap_or(na::UnitQuaternion::identity().to_homogeneous());*/ + let anim_transform = na::Matrix4::identity() .append_translation(&pos) * - quat.to_homogeneous() * + //quat.to_homogeneous() * rotz.to_homogeneous(); //let applied_transform = self.local_bind_transform * anim_transform; - self.world_bind_transform = parent_transform * self.local_bind_transform * anim_transform; + self.world_anim_transform = parent_transform * self.local_bind_transform * anim_transform; } else { - self.world_bind_transform = parent_transform * self.local_bind_transform; + self.world_anim_transform = parent_transform * self.local_bind_transform; } for ch in self.children.iter_mut() { - ch.recompute_world_transform(self.world_bind_transform, bone_sample_map, i); + ch.recompute_world_anim_transform(self.world_anim_transform, bone_sample_map, i); } } - fn get_world_pos(&self) -> na::Vector3 { + fn get_world_bind_pos(&self) -> na::Vector3 { self.world_bind_transform.column(3).xyz() } + + fn get_world_anim_pos(&self) -> na::Vector3 { + self.world_anim_transform.column(3).xyz() + } } fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { - let v = bone.get_world_pos(); - let points = vec![Point3D::from([v[0], v[1], v[2]])]; + let v = bone.get_world_anim_pos(); // Generate line strips let strips = bone .children .iter() .map(|c| { - let cv = c.get_world_pos(); + let cv = c.get_world_anim_pos(); vec![[v[0], v[1], v[2]], [cv.x, cv.y, cv.z]].into() }) .collect::>(); - MsgSender::new(bone.name) - .with_component(&points) + // Add vertex + MsgSender::new(format!("world/{}", bone.name)) + .with_component(&[ + Point3D::from([v[0], v[1], v[2]]) + ]) .unwrap() + /*.with_splat(ViewCoordinates::from_up_and_handedness( + SignedAxis3::POSITIVE_Z, + Handedness::Right)) + .unwrap()*/ + //.with_splat(Radius(1.0)) + //.unwrap() .with_time(Timeline::new_sequence("frame"), i as i64) .send(session) .unwrap(); - MsgSender::new(format!("{}_lines", bone.name)) + // Add lines from node to children + MsgSender::new(format!("world/{}/lines", bone.name)) .with_component(&strips) .unwrap() + /*.with_splat(ViewCoordinates::from_up_and_handedness( + SignedAxis3::POSITIVE_Z, + Handedness::Right)) + .unwrap()*/ + .with_time(Timeline::new_sequence("frame"), i as i64) + .send(session) + .unwrap(); + + // Add direction arrow (not working) + MsgSender::new(format!("world/{}/arrows", bone.name)) + .with_component(&[ + Arrow3D { + origin: Vec3D([v[0], v[1], v[2]]), + vector: { + let v: na::Vector3<_> = bone + .world_bind_transform + .transform_vector(&na::Vector3::from_element(0.25)); + + /*let rotation = na::UnitQuaternion::from_matrix(&bone + .world_bind_transform.fixed_view::<3, 3>(0, 0).into() + ); + + let (i, j, k) = rotation.euler_angles(); + let v = na::Vector3::new(i, j, k);*/ + + Vec3D([v[0], v[1], v[2]]) + } + } + ]) + .unwrap() + //.with_splat(Scalar(10.)).unwrap() + .with_splat(Radius(0.01)).unwrap() + .with_splat(ColorRGBA::from_rgb(0, 255, 0)).unwrap() + /*.with_splat(ViewCoordinates::from_up_and_handedness( + SignedAxis3::POSITIVE_Z, + Handedness::Right)) + .unwrap()*/ .with_time(Timeline::new_sequence("frame"), i as i64) .send(session) .unwrap(); From 00b89818744ca63a85bd5025847fc5ce44cc89ea Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Feb 2023 00:23:47 -0500 Subject: [PATCH 028/113] Decompose local transform and apply sample transforms directly --- utils/anim_preview/src/main.rs | 96 +++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index a78c41c6..c8e61948 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -114,7 +114,7 @@ fn main() -> Result<(), Box> { SignedAxis3::POSITIVE_Z, Handedness::Right)) .unwrap() - .with_splat(Transform::Rigid3({ + /*.with_splat(Transform::Rigid3({ let q = na::UnitQuaternion ::from_axis_angle( &na::Vector3::z_axis(), @@ -126,7 +126,7 @@ fn main() -> Result<(), Box> { ..Default::default() } })) - .unwrap() + .unwrap()*/ .with_timeless(true) .send(&mut session) .unwrap(); @@ -278,7 +278,10 @@ fn main() -> Result<(), Box> { //char_bone_sample. }*/ } else { - let (points, lines) = generate_bone_points(&root_bone); + add_bones_to_session(&root_bone, &mut session, 0); + + // Can probably delete + /*let (points, lines) = generate_bone_points(&root_bone); MsgSender::new(root_bone.name) .with_component(&points)? @@ -288,7 +291,7 @@ fn main() -> Result<(), Box> { MsgSender::new(format!("{}_lines", root_bone.name)) .with_component(&lines)? .send(&mut session) - .unwrap(); + .unwrap();*/ } } @@ -441,13 +444,15 @@ impl<'a> BoneNode<'a> { fn recompute_world_anim_transform(&mut self, parent_transform: na::Matrix4, bone_sample_map: &HashMap<&str, (&CharBoneSample, &Vec)>, i: usize) { if let Some((sample, _)) = bone_sample_map.get(self.name) { - // TODO: Multiple by bone weight? + // Decompose original local transform + let (mut trans, mut rotate, scale) = decompose_trs(self.local_bind_transform); + + // TODO: Multiply by bone weight? let pos = sample .pos .as_ref() .and_then(|(_, p)| p.get(i).or_else(|| p.last())) - .map(|v| na::Vector3::new(v.x, v.y, v.z)) - .unwrap_or_default(); + .map(|v| na::Vector3::new(v.x, v.y, v.z)); let quat = sample .quat @@ -460,18 +465,37 @@ impl<'a> BoneNode<'a> { q.y, q.z ) - )) - .unwrap_or(na::UnitQuaternion::identity()); + )); let rotz = sample .rotz .as_ref() - .and_then(|(_, r)| r.get(i).or_else(|| r.last())) - .map(|z| na::UnitQuaternion::from_axis_angle( + .and_then(|(_, r)| r.get(i).or_else(|| r.last())); + /*.map(|z| na::UnitQuaternion::from_axis_angle( &na::Vector3::z_axis(), std::f32::consts::PI * z - )) - .unwrap_or(na::UnitQuaternion::identity()); + ));*/ + + // Override values if found + if let Some(pos) = pos { + trans = pos; + } + + if let Some(quat) = quat { + rotate = quat; + } + + if let Some(rotz) = rotz { + let (roll, pitch, yaw) = rotate.euler_angles(); + //println!("({}, {}, {})", roll, pitch, yaw); + + rotate = na::UnitQuaternion::from_euler_angles(roll, pitch, std::f32::consts::PI * rotz); + + /*rotate = na::UnitQuaternion::from_axis_angle( + &na::Vector3::z_axis(), + *rotz + );*/ + } /*let rotz2 = sample .rotz @@ -480,13 +504,19 @@ impl<'a> BoneNode<'a> { .map(|z| na::Rotation3::from_axis_angle(&na::Vector3::z_axis(), *z).to_homogeneous()) .unwrap_or(na::UnitQuaternion::identity().to_homogeneous());*/ - let anim_transform = na::Matrix4::identity() + /*let anim_transform = na::Matrix4::identity() .append_translation(&pos) * - //quat.to_homogeneous() * - rotz.to_homogeneous(); + quat.to_homogeneous() * + rotz.to_homogeneous();*/ + + // Re-compute local transform + let trs: na::Matrix4::<_> = (na::Matrix4::identity() + .append_translation(&trans) * + rotate.to_homogeneous()) + .append_nonuniform_scaling(&scale); //let applied_transform = self.local_bind_transform * anim_transform; - self.world_anim_transform = parent_transform * self.local_bind_transform * anim_transform; + self.world_anim_transform = parent_transform * trs; // anim_transform; } else { self.world_anim_transform = parent_transform * self.local_bind_transform; } @@ -524,6 +554,19 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { Point3D::from([v[0], v[1], v[2]]) ]) .unwrap() + .with_splat(Transform::Rigid3({ + let q = na::UnitQuaternion + ::from_axis_angle( + &na::Vector3::z_axis(), + std::f32::consts::PI + ); + + Rigid3 { + rotation: Quaternion::new(q.i, q.j, q.k, q.w), + ..Default::default() + } + })) + .unwrap() /*.with_splat(ViewCoordinates::from_up_and_handedness( SignedAxis3::POSITIVE_Z, Handedness::Right)) @@ -554,7 +597,7 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { vector: { let v: na::Vector3<_> = bone .world_bind_transform - .transform_vector(&na::Vector3::from_element(0.25)); + .transform_vector(&na::Vector3::from_element(0.5)); /*let rotation = na::UnitQuaternion::from_matrix(&bone .world_bind_transform.fixed_view::<3, 3>(0, 0).into() @@ -569,7 +612,7 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { ]) .unwrap() //.with_splat(Scalar(10.)).unwrap() - .with_splat(Radius(0.01)).unwrap() + .with_splat(Radius(0.05)).unwrap() .with_splat(ColorRGBA::from_rgb(0, 255, 0)).unwrap() /*.with_splat(ViewCoordinates::from_up_and_handedness( SignedAxis3::POSITIVE_Z, @@ -582,4 +625,19 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { for ch in bone.children.iter() { add_bones_to_session(ch, session, i); } +} + +// TODO: Move to shared part in lib +fn decompose_trs(mat: na::Matrix4) -> (na::Vector3, na::UnitQuaternion, na::Vector3) { + // Decompose matrix to T*R*S + let translate = mat.column(3).xyz(); + let rotation = na::UnitQuaternion::from_matrix(&mat.fixed_view::<3, 3>(0, 0).into()); + + let scale = na::Vector3::new( + mat.column(0).magnitude(), + mat.column(1).magnitude(), + mat.column(2).magnitude(), + ); + + (translate, rotation, scale) } \ No newline at end of file From a46f1ea6202174d636172de6c9b856efaf56c4c1 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Feb 2023 21:53:30 -0500 Subject: [PATCH 029/113] Fix reading in packed rotation values for char clips --- core/grim/src/scene/char_bones_samples/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/grim/src/scene/char_bones_samples/mod.rs b/core/grim/src/scene/char_bones_samples/mod.rs index 04a789d0..360f3d02 100644 --- a/core/grim/src/scene/char_bones_samples/mod.rs +++ b/core/grim/src/scene/char_bones_samples/mod.rs @@ -134,11 +134,11 @@ impl CharBonesSamples { let read_packed_f32 = if sys_info.endian.eq(&IOEndian::Big) { |data: [u8; 2]| -> f32 { - ((u16::from_be_bytes(data) as f32) / 32767.0).max(-1.0) + ((i16::from_be_bytes(data) as f32) / 32767.0).max(-1.0) } } else { |data: [u8; 2]| -> f32 { - ((u16::from_le_bytes(data) as f32) / 32767.0).max(-1.0) + ((i16::from_le_bytes(data) as f32) / 32767.0).max(-1.0) } }; @@ -178,9 +178,9 @@ impl CharBonesSamples { }, s @ 6 => { // Read packed data - let x = read_packed_f32([sample[i ], sample[i + 1]]); - let y = read_packed_f32([sample[i + 2], sample[i + 3]]); - let z = read_packed_f32([sample[i + 4], sample[i + 5]]); + let x = read_packed_f32([sample[i ], sample[i + 1]]) * 1345.; // TODO: Investigate better constant + let y = read_packed_f32([sample[i + 2], sample[i + 3]]) * 1345.; + let z = read_packed_f32([sample[i + 4], sample[i + 5]]) * 1345.; i += s as usize; Vector3 { x, y, z } From bada59a639df9ea73e890c243973c98958280b78 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Feb 2023 21:57:44 -0500 Subject: [PATCH 030/113] Decompose existing local trs and apply anim transforms --- utils/anim_preview/src/main.rs | 100 +++++++++++---------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index c8e61948..f35d8596 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -300,43 +300,13 @@ fn main() -> Result<(), Box> { Ok(()) } -fn generate_bone_points(bone: &BoneNode) -> (Vec, Vec) { - let mut points = Vec::new(); - let mut lines = Vec::new(); - - //let v: na::Vector3 = bone.transform.transform_vector(&na::Vector3::zeros()); - let v = bone.world_bind_transform.column(3).xyz(); - points.push(Point3D::from([v[0], v[1], v[2]])); - - // Generate line strips - let mut strips = bone - .children - .iter() - .map(|c| { - let cv = c.world_bind_transform.column(3).xyz(); - vec![[v[0], v[1], v[2]], [cv.x, cv.y, cv.z]].into() - }) - .collect::>(); - - lines.append(&mut strips); - - for child in bone.children.iter() { - let (mut child_points, mut child_lines) = generate_bone_points(child); - - points.append(&mut child_points); - lines.append(&mut child_lines); - } - - (points, lines) -} - pub struct BoneNode<'a> { pub name: &'a str, pub object: Option<&'a dyn Trans>, pub children: Vec>, pub local_bind_transform: na::Matrix4, - pub world_bind_transform: na::Matrix4, - pub world_anim_transform: na::Matrix4, + pub inverse_bind_transform: na::Matrix4, + pub anim_transform: na::Matrix4, } impl<'a> BoneNode<'a> { @@ -381,12 +351,12 @@ impl<'a> BoneNode<'a> { object: None, children: Vec::new(), local_bind_transform: na::Matrix4::identity(), - world_bind_transform: na::Matrix4::identity(), - world_anim_transform: na::Matrix4::identity(), + inverse_bind_transform: na::Matrix4::identity().try_inverse().unwrap(), + anim_transform: na::Matrix4::identity(), }; // Find bones that belong to object dir - root.children = root.find_child_nodes(&bones, &child_map); + root.children = root.find_child_nodes(root.local_bind_transform, &bones, &child_map); if root.children.is_empty() { return None; @@ -395,7 +365,7 @@ impl<'a> BoneNode<'a> { Some(root) } - fn find_child_nodes(&self, bone_map: &HashMap<&str, &'a dyn Trans>, child_map: &HashMap<&str, Vec<&'a dyn Trans>>) -> Vec> { + fn find_child_nodes(&self, parent_transform: na::Matrix4, bone_map: &HashMap<&str, &'a dyn Trans>, child_map: &HashMap<&str, Vec<&'a dyn Trans>>) -> Vec> { let parent_name = self.name; let Some(children) = child_map.get(parent_name) else { @@ -427,22 +397,26 @@ impl<'a> BoneNode<'a> { }) .unwrap_or(na::Matrix4::identity()); + let current_transform = parent_transform * local_transform; + let mut bone = BoneNode { name: c.get_name().as_str(), object: trans_obj, children: Vec::new(), local_bind_transform: local_transform, - world_bind_transform: self.world_bind_transform * local_transform, - world_anim_transform: self.world_bind_transform * local_transform, + inverse_bind_transform: current_transform.try_inverse().unwrap(), + anim_transform: current_transform, }; - bone.children = bone.find_child_nodes(bone_map, child_map); + bone.children = bone.find_child_nodes(current_transform, bone_map, child_map); bone }) .collect() } fn recompute_world_anim_transform(&mut self, parent_transform: na::Matrix4, bone_sample_map: &HashMap<&str, (&CharBoneSample, &Vec)>, i: usize) { + let current_transform; + if let Some((sample, _)) = bone_sample_map.get(self.name) { // Decompose original local transform let (mut trans, mut rotate, scale) = decompose_trs(self.local_bind_transform); @@ -453,6 +427,7 @@ impl<'a> BoneNode<'a> { .as_ref() .and_then(|(_, p)| p.get(i).or_else(|| p.last())) .map(|v| na::Vector3::new(v.x, v.y, v.z)); + //.unwrap_or_else(|| na::Vector3::zeros()); let quat = sample .quat @@ -466,15 +441,17 @@ impl<'a> BoneNode<'a> { q.z ) )); + //.unwrap_or_else(|| na::UnitQuaternion::identity()); let rotz = sample .rotz .as_ref() - .and_then(|(_, r)| r.get(i).or_else(|| r.last())); - /*.map(|z| na::UnitQuaternion::from_axis_angle( + .and_then(|(_, r)| r.get(i).or_else(|| r.last())) + .map(|z| na::UnitQuaternion::from_axis_angle( &na::Vector3::z_axis(), std::f32::consts::PI * z - ));*/ + )); + //.unwrap_or_else(|| na::UnitQuaternion::identity()); // Override values if found if let Some(pos) = pos { @@ -489,49 +466,38 @@ impl<'a> BoneNode<'a> { let (roll, pitch, yaw) = rotate.euler_angles(); //println!("({}, {}, {})", roll, pitch, yaw); - rotate = na::UnitQuaternion::from_euler_angles(roll, pitch, std::f32::consts::PI * rotz); + //rotate = na::UnitQuaternion::from_euler_angles(roll, pitch, std::f32::consts::PI * rotz); + //rotate.renormalize(); /*rotate = na::UnitQuaternion::from_axis_angle( &na::Vector3::z_axis(), - *rotz + std::f32::consts::PI * rotz );*/ - } - /*let rotz2 = sample - .rotz - .as_ref() - .and_then(|(_, r)| r.get(i).or_else(|| r.last())) - .map(|z| na::Rotation3::from_axis_angle(&na::Vector3::z_axis(), *z).to_homogeneous()) - .unwrap_or(na::UnitQuaternion::identity().to_homogeneous());*/ - - /*let anim_transform = na::Matrix4::identity() - .append_translation(&pos) * - quat.to_homogeneous() * - rotz.to_homogeneous();*/ + rotate *= rotz; + } - // Re-compute local transform - let trs: na::Matrix4::<_> = (na::Matrix4::identity() + // Compute local trs transform + let anim_transform = (na::Matrix4::identity() .append_translation(&trans) * rotate.to_homogeneous()) .append_nonuniform_scaling(&scale); - //let applied_transform = self.local_bind_transform * anim_transform; - self.world_anim_transform = parent_transform * trs; // anim_transform; + + current_transform = parent_transform * anim_transform; } else { - self.world_anim_transform = parent_transform * self.local_bind_transform; + current_transform = parent_transform * self.local_bind_transform; } for ch in self.children.iter_mut() { - ch.recompute_world_anim_transform(self.world_anim_transform, bone_sample_map, i); + ch.recompute_world_anim_transform(current_transform, bone_sample_map, i); } - } - fn get_world_bind_pos(&self) -> na::Vector3 { - self.world_bind_transform.column(3).xyz() + self.anim_transform = current_transform; } fn get_world_anim_pos(&self) -> na::Vector3 { - self.world_anim_transform.column(3).xyz() + self.anim_transform.column(3).xyz() } } @@ -596,7 +562,7 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { origin: Vec3D([v[0], v[1], v[2]]), vector: { let v: na::Vector3<_> = bone - .world_bind_transform + .anim_transform .transform_vector(&na::Vector3::from_element(0.5)); /*let rotation = na::UnitQuaternion::from_matrix(&bone From ea6b149ac4fa871abd6f3b40f69d663ac01cae7a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Feb 2023 23:26:35 -0500 Subject: [PATCH 031/113] Fix anim export for gltf --- core/grim/src/model/export.rs | 130 ++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 46 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 6ac34c9b..7ce42627 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -501,7 +501,7 @@ impl GltfExporter { let node_index = gltf.nodes.len(); // Get + compute transform matrix - let mat = match (self.get_transform(name), depth) { + let node_matrix = match (self.get_transform(name), depth) { (Some(trans), 0) => { let m = trans.get_world_xfm(); @@ -530,25 +530,46 @@ impl GltfExporter { _ => na::Matrix4::identity() }; + // Deconstruct into individual parts + let (translate, rotation, scale) = decompose_trs(node_matrix); + gltf.nodes.push(gltf_json::Node { camera: None, children: None, extensions: None, extras: None, - matrix: if mat.is_identity(f32::EPSILON) { - // Don't add identities + matrix: None, + mesh: None, + name: Some(name.to_owned()), + // Don't add identities + rotation: if rotation.eq(&na::UnitQuaternion::identity()) { None } else { - mat - .as_slice() - .try_into() - .ok() + Some(json::scene::UnitQuaternion([ + rotation[0], + rotation[1], + rotation[2], + rotation[3] + ])) + }, + scale: if scale.eq(&na::Vector3::from_element(1.0)) { + None + } else { + Some([ + scale[0], + scale[1], + scale[2] + ]) + }, + translation: if translate.eq(&na::Vector3::zeros()) { + None + } else { + Some([ + translate[0], + translate[1], + translate[2] + ]) }, - mesh: None, - name: Some(name.to_owned()), - rotation: None, - scale: None, - translation: None, skin: None, weights: None, }); @@ -770,6 +791,30 @@ impl GltfExporter { &n.children, parent_mat * n.matrix .map(|m| na::Matrix4::from_column_slice(&m)) + .or_else(|| { + // Re-construct into trs + // TODO: Probably don't deconstruct in first place + let trans = n.translation + .map(na::Vector3::from) + .unwrap_or_else(na::Vector3::zeros); + + let rotate = n.rotation + .map(|json::scene::UnitQuaternion([x, y, z, w])| + na::UnitQuaternion::from_quaternion( + na::Quaternion::new(w, x, y, z) + ) + ) + .unwrap_or_else(|| na::UnitQuaternion::identity()); + + let scale = n.scale + .map(na::Vector3::from) + .unwrap_or_else(|| na::Vector3::from_element(1.0)); + + Some((na::Matrix4::identity() + .append_translation(&trans) * + rotate.to_homogeneous()) + .append_nonuniform_scaling(&scale)) + }) .unwrap_or_default() )) .unwrap(); @@ -1643,22 +1688,24 @@ impl GltfExporter { }; // Get existing matrix for node - let node_matrix = gltf - .nodes[node_idx] - .matrix - .map(|m| na::Matrix4::from_column_slice(&m)) - .unwrap_or(na::Matrix4::identity() /*super::MILOSPACE_TO_GLSPACE*/); + let node = &gltf.nodes[node_idx]; - // Decompose matrix to T*R*S - let (translate, rotation, scale) = decompose_trs(node_matrix); + let node_trans = node.translation + .map(na::Vector3::from) + .unwrap_or_else(na::Vector3::zeros); - // Update node - if let Some(node) = gltf.nodes.get_mut(node_idx) { - node.matrix = None; - node.translation = Some([translate[0], translate[1], translate[2]]); - node.rotation = Some(json::scene::UnitQuaternion([rotation[0], rotation[1], rotation[2], rotation[3]])); - node.scale = Some([scale[0], scale[1], scale[2]]) - } + let node_rotate = node.rotation + .map(|json::scene::UnitQuaternion([x, y, z, w])| + na::UnitQuaternion::from_quaternion( + na::Quaternion::new(w, x, y, z) + ) + ) + .unwrap_or_else(|| na::UnitQuaternion::identity()); + + // TODO: Add scaling transform samples... + let node_scale = node.scale + .map(na::Vector3::from) + .unwrap_or_else(|| na::Vector3::from_element(1.0)); // Compute samples as matrices //let translate_samples = bone.pos.take().map(|(pw, p)| p.into_iter().map(|v| na::Matrix4::new_translation(&na::Vector3::new(v.x, v.y, v.z)))); @@ -1735,19 +1782,20 @@ impl GltfExporter { extras: None });*/ + const FPS: f32 = 1. / 30.; + // Add translations (.pos) if let Some((w, samples)) = bone.pos.take() { let input_idx = acc_builder.add_scalar( format!("{}_{}_translation_input", clip_name, bone_name), //frames.iter().map(|f| *f) - samples.iter().enumerate().map(|(i, _)| i as f32) + samples.iter().enumerate().map(|(i, _)| (i as f32) * FPS) ).unwrap(); let output_idx = acc_builder.add_array( format!("{}_{}_translation_output", clip_name, bone_name), samples.into_iter().map(|s| { - let mut v = na::Vector3::new(s.x * w, s.y * w, s.z * w); - v += translate; + let v = na::Vector3::new(s.x * w, s.y * w, s.z * w); [v.x, v.y, v.z] }) @@ -1783,7 +1831,7 @@ impl GltfExporter { let output_idx = acc_builder.add_array( format!("{}_{}_translation_output", clip_name, bone_name), { - let v = translate; + let v = node_trans; vec![[v.x, v.y, v.z]] } ).unwrap(); @@ -1856,7 +1904,7 @@ impl GltfExporter { // Combined rotations let mut rotation_samples = (0..rotation_sample_count) - .map(|_| rotation) + .map(|_| node_rotate) /*.map(|_| { let q = rotation.as_vector(); na::Quaternion::new(q[3], q[0], q[1], q[2]) @@ -1880,7 +1928,7 @@ impl GltfExporter { //*rot = *rot * q; //*rot = rot.rotation_to(&na::UnitQuaternion::from_quaternion(q)); - *rot = *rot * na::UnitQuaternion::from_quaternion(q); + *rot = na::UnitQuaternion::from_quaternion(q); //*rot = na::UnitQuaternion::from_quaternion(rot.normalize()); } } @@ -1897,16 +1945,17 @@ impl GltfExporter { //let qq = na::Quaternion::new(q[3], q[0], q[1], q[2]); - *rot = *rot * q; + *rot *= q; } } // Add all rotations + // TODO: Add empty rotation sample? if rotation_samples.len() > 0 { let input_idx = acc_builder.add_scalar( format!("{}_{}_rotation_input", clip_name, bone_name), //frames.iter().map(|f| *f) - rotation_samples.iter().enumerate().map(|(i, _)| i as f32) + rotation_samples.iter().enumerate().map(|(i, _)| (i as f32) * FPS) ).unwrap(); let output_idx = acc_builder.add_array( @@ -1963,25 +2012,14 @@ fn align_to_multiple_of_four(n: usize) -> usize { fn decompose_trs(mat: na::Matrix4) -> (na::Vector3, na::UnitQuaternion, na::Vector3) { // Decompose matrix to T*R*S let translate = mat.column(3).xyz(); - //let cc = node_matrix.fixed_view::<3, 3>(0, 0); - //let rot = na::UnitQuaternion::from_matrix(&cc.into()); let rotation = na::UnitQuaternion::from_matrix(&mat.fixed_view::<3, 3>(0, 0).into()); - //let scale = mat.column(0).xyz().component_mul(&mat.column(1).xyz()).component_mul(&mat.column(2).xyz()); + let scale = na::Vector3::new( mat.column(0).magnitude(), mat.column(1).magnitude(), mat.column(2).magnitude(), ); - /*let smx = mat.column(0).magnitude(); - let smy = mat.column(1).magnitude(); - let smz = mat.column(2).magnitude(); - - let scale = na::Vector3::new(smx, smy, smz); - - let rot_base = na::UnitQuaternion::from_matrix(&mat.fixed_view::<3, 3>(0, 0).into());*/ - - (translate, rotation, scale) } From 9cfe5579260b8818f0007e2f695958029b7e5204 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 23 Feb 2023 20:30:17 -0500 Subject: [PATCH 032/113] Ignore mesh shadows for gltf export --- core/grim/src/model/export.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 7ce42627..e1d8010c 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -452,6 +452,11 @@ impl GltfExporter { ); }, Object::Mesh(mesh) => { + if mesh.name.contains("shadow") { + // Skip shadows for now + continue; + } + self.meshes.insert( name, MappedObject::new(mesh, parent.clone()) @@ -804,7 +809,7 @@ impl GltfExporter { na::Quaternion::new(w, x, y, z) ) ) - .unwrap_or_else(|| na::UnitQuaternion::identity()); + .unwrap_or_else(na::UnitQuaternion::identity); let scale = n.scale .map(na::Vector3::from) @@ -1943,8 +1948,6 @@ impl GltfExporter { std::f32::consts::PI * (z * w) ); - //let qq = na::Quaternion::new(q[3], q[0], q[1], q[2]); - *rot *= q; } } From b8f29caf108bad11a985d9ebab9c97cd2640e526 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 23 Feb 2023 20:30:34 -0500 Subject: [PATCH 033/113] Log extra input milo files --- apps/cli/mesh_tool/src/apps/milo2gltf.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/cli/mesh_tool/src/apps/milo2gltf.rs b/apps/cli/mesh_tool/src/apps/milo2gltf.rs index 7159938e..c27ee8c9 100644 --- a/apps/cli/mesh_tool/src/apps/milo2gltf.rs +++ b/apps/cli/mesh_tool/src/apps/milo2gltf.rs @@ -47,7 +47,16 @@ impl SubApp for Milo2GltfApp { ..Default::default() }); exporter.add_milo_from_path(milo_path)?; + + // Add extra milos for extra_path in self.extra_milo_paths.iter() { + let extra_path = PathBuf::from(extra_path); + let extra_path_file_name = extra_path + .file_name() + .and_then(|f| f.to_str()) + .unwrap(); + + println!("Adding {}", extra_path_file_name); exporter.add_milo_from_path(extra_path)?; } From 5b1477708ff15c79d0a6b9fe8c703b38bbc4ea7c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 23 Feb 2023 21:51:57 -0500 Subject: [PATCH 034/113] Ignore both lod and shadow objects --- core/grim/src/model/export.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index e1d8010c..4849cdd1 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -429,7 +429,25 @@ impl GltfExporter { let entries = dir_entry.entries.drain(..).collect::>(); let parent = Rc::new(dir_entry); + // Ignore groups + meshes in lower lods or shadow + // TODO: Work out better way to filter + let ignored_objects = entries + .iter() + .filter_map(|e| match (e, e.get_name()) { + (Object::Group(grp), n) if ["shadow", "lod1", "lod2", "lod01", "lod02", "LOD01", "LOD02"] + .iter().any(|f| n.contains(f)) => { + Some(grp.objects.to_owned().into_iter().chain([n.to_owned()])) + }, + _ => None + }) + .flatten() + .collect::>(); + for entry in entries { + if ignored_objects.contains(entry.get_name()) { + continue; + } + let name = entry.get_name().to_owned(); match entry { @@ -452,11 +470,6 @@ impl GltfExporter { ); }, Object::Mesh(mesh) => { - if mesh.name.contains("shadow") { - // Skip shadows for now - continue; - } - self.meshes.insert( name, MappedObject::new(mesh, parent.clone()) From 2c443e97778ce65efe60e98380646890d93f2d55 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 26 Feb 2023 22:12:41 -0500 Subject: [PATCH 035/113] Add basic open wav function --- core/grim/src/audio/wav.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/core/grim/src/audio/wav.rs b/core/grim/src/audio/wav.rs index 7407ae57..2d15f6cd 100644 --- a/core/grim/src/audio/wav.rs +++ b/core/grim/src/audio/wav.rs @@ -1,5 +1,5 @@ use crate::io::create_new_file; -use wav::{Header, WAV_FORMAT_PCM, write as wav_write}; +use wav::{BitDepth, Header, WAV_FORMAT_PCM, read as wav_read, write as wav_write}; pub struct WavEncoder<'a> { data: &'a [i16], @@ -24,4 +24,37 @@ impl<'a> WavEncoder<'a> { wav_write(header, &bit_data, &mut out_file) } +} + +pub fn open_wav>(wav_path: T) -> std::io::Result<(u32, Vec>)> { + let mut wav_file = std::fs::OpenOptions::new() + .read(true) + .open(&wav_path)?; + + let (header, bitdepth) = wav_read(&mut wav_file)?; + + let channel_count = header.channel_count as usize; + let sample_count = match &bitdepth { + BitDepth::Sixteen(s) => s.len() / channel_count, + _ => todo!() + }; + + let mut channels = vec![vec![0i16; sample_count]; channel_count]; + let samples = bitdepth.as_sixteen().unwrap(); + + /*let ss = samples + .chunks(channel_count) + .enumerate() + .map(|c| c.iter().enumerate()) + .enumerate() + .for_each(|()|);*/ + + // De-interleave samples + for (s_idx, cs) in samples.chunks_exact(channel_count).enumerate() { + for (c_idx, s) in cs.iter().enumerate() { + channels[c_idx][s_idx] = *s; + } + } + + Ok((header.sampling_rate, channels)) } \ No newline at end of file From fa95818de51e98ecab0ad97874943663374f59e7 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 26 Feb 2023 22:14:14 -0500 Subject: [PATCH 036/113] Add previewer for vox weights + audio samples --- utils/anim_preview/Cargo.toml | 3 +- utils/anim_preview/src/main.rs | 27 +----- utils/lipsync_preview/Cargo.toml | 13 +++ utils/lipsync_preview/src/app.rs | 146 ++++++++++++++++++++++++++++++ utils/lipsync_preview/src/main.rs | 40 ++++++++ utils/shared/Cargo.toml | 8 ++ utils/shared/src/lib.rs | 31 +++++++ 7 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 utils/lipsync_preview/Cargo.toml create mode 100644 utils/lipsync_preview/src/app.rs create mode 100644 utils/lipsync_preview/src/main.rs create mode 100644 utils/shared/Cargo.toml create mode 100644 utils/shared/src/lib.rs diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index d1f55551..481c7010 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -8,4 +8,5 @@ edition.workspace = true grim = { workspace = true } keyframe = "1.1.1" nalgebra = "0.32.1" -rerun = "0.2.0" \ No newline at end of file +rerun = "0.2.0" +shared = { path = "../shared" } \ No newline at end of file diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index f35d8596..d8af1bfe 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -22,6 +22,8 @@ use rerun::{ time::Timeline }; +use shared::*; + #[derive(Clone, Default)] struct Vec3Collection(Vec); @@ -46,31 +48,6 @@ impl CanTween for Vec3Collection { } } -struct MiloLoader { - pub path: PathBuf, - pub sys_info: SystemInfo, - pub obj_dir: ObjectDir, -} - -impl MiloLoader { - pub fn from_path(milo_path: PathBuf) -> Result> { - // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); - let milo = MiloArchive::from_stream(&mut stream)?; - - // Unpack milo - let system_info = SystemInfo::guess_system_info(&milo, &milo_path); - let mut obj_dir = milo.unpack_directory(&system_info)?; - obj_dir.unpack_entries(&system_info)?; - - Ok(Self { - path: milo_path, - sys_info: system_info, - obj_dir - }) - } -} - fn main() -> Result<(), Box> { let args: Vec<_> = env::args().skip(1).collect(); diff --git a/utils/lipsync_preview/Cargo.toml b/utils/lipsync_preview/Cargo.toml new file mode 100644 index 00000000..5db0833f --- /dev/null +++ b/utils/lipsync_preview/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "lipsync_preview" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +grim = { workspace = true, features = [ "audio" ] } +eframe = "0.21.3" +keyframe = "1.1.1" +nalgebra = "0.32.1" +#rerun = "0.2.0" +shared = { path = "../shared" } \ No newline at end of file diff --git a/utils/lipsync_preview/src/app.rs b/utils/lipsync_preview/src/app.rs new file mode 100644 index 00000000..afc73bd0 --- /dev/null +++ b/utils/lipsync_preview/src/app.rs @@ -0,0 +1,146 @@ +use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::io::prelude::*; +use std::path::PathBuf; + +use grim::{Platform, SystemInfo}; +use grim::audio::open_wav; +use grim::io::*; +use grim::scene::{Anim, CharBoneSample, Object, ObjectDir, PackedObject, MeshAnim, MiloObject, Trans, Vector3}; + +use eframe::{egui::{self, Align, Align2, Color32, FontId, Pos2, RichText, Visuals, Widget}, glow}; +use egui::widgets::global_dark_light_mode_switch; + +use shared::*; + +#[derive(Default)] +pub struct LipsyncApp { + args: Vec, + weight_points: Vec, + sample_points: Vec>, + selected_channel: usize, +} + +impl LipsyncApp { + pub fn load_args(&mut self, args: &Vec) -> Result<(), Box> { + let weights_data = { + let weights_path = PathBuf::from(&args[0]); + + let mut weights_file = std::fs::OpenOptions::new() + .read(true) + .open(&weights_path)?; + + let mut buff = Vec::new(); + weights_file.read_to_end(&mut buff)?; + + buff + }; + + let (sample_rate, wav_channels) = { + let wav_path = PathBuf::from(&args[1]); + open_wav(&wav_path)? + }; + + let channel_idx = args[2].parse::().unwrap_or(1); + + self.selected_channel = channel_idx - 1; + + self.weight_points = weights_data + .into_iter() + .map(|w| w as f64 / 255.) + .collect(); + + self.sample_points = wav_channels + .into_iter() + .map(|sams| sams + .into_iter() + .step_by(sample_rate as usize / 60) + .map(|s| (s as f64 / i16::MAX as f64).abs()) + /*.chunks(sample_rate as usize / 60) + .map(|sc| sc + .iter() + .map(|s| (*s as f64 / i16::MAX as f64).abs()) + .sum::() / sc.len() as f64) + //.max_by(|a, b| a.total_cmp(b)).unwrap_or_default())*/ + .collect()) + .collect(); + + /*for (i, w) in weights_data.into_iter().enumerate() { + let value = w as f64 / 255.; + + let r = w; + let g = 255 - r; + + /*MsgSender::new(format!("weights")) + .with_time(Timeline::new("milliseconds", TimeType::Time), Time::from_seconds_since_epoch(i as f64 / 60.)) + .with_splat(Scalar(w as f64 / 255.))? + .with_splat(ColorRGBA::from_rgb(r, g, 0))? + .send(&mut session)?;*/ + }*/ + + /*for (c_idx, channel) in wav_channels.into_iter().enumerate() { + if c_idx != channel_idx { + continue; + } + + for (s_idx, sample) in channel.into_iter().enumerate() { + let normalized = sample as f64 / i32::MAX as f64; + let time_s = s_idx as f64 / sample_rate as f64; + + /*MsgSender::new(format!("audio_channel_{c_idx}")) + .with_time(Timeline::new("milliseconds", TimeType::Time), Time::from_seconds_since_epoch(time_s)) + .with_splat(Scalar(normalized))? + .with_splat(ColorRGBA::from_rgb(0, 0, 255 - (20 * c_idx as u8)))? + .send(&mut session)?;*/ + } + }*/ + + Ok(()) + } +} + +impl eframe::App for LipsyncApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + let frame = egui::containers::Frame::default() + .inner_margin(egui::Vec2::ZERO); + + egui::CentralPanel::default().frame(frame).show(ctx, |ui| { + let x_grid_spacer = egui::plot::uniform_grid_spacer(|_g| [60. * 60., 60., 20.]); + let y_grid_spacer = egui::plot::uniform_grid_spacer(|_g| [0.5, 0.1, 0.05]); + + egui::plot::Plot::new("main_plot") + .center_y_axis(false) + .min_size(egui::Vec2::new(0., 1.)) + .x_grid_spacer(x_grid_spacer) + .y_grid_spacer(y_grid_spacer) + //.set_margin_fraction(egui::Vec2::ZERO) + .show(ui, |plot_ui| { + // Draw audio samples + if let Some(channel_samples) = self.sample_points.get(self.selected_channel) { + let sample_points = convert_to_egui_points(channel_samples, 1, false); + //plot_ui.points(sample_points.name("audio").color(Color32::BLUE)); + + plot_ui.line(egui::plot::Line::new(sample_points).name("audio").color(Color32::BLUE)); + } + + // Draw weights + let weight_points = convert_to_egui_points(&self.weight_points, 1, false); + //plot_ui.points(egui::plot::Points::new(weight_points).name("weights").color(Color32::GREEN)); + + plot_ui.line(egui::plot::Line::new(weight_points).name("weights").color(Color32::GREEN)); + }); + }); + } +} + +fn convert_to_egui_points(points: &Vec, step: usize, hide_zero: bool) -> egui::plot::PlotPoints { + let series = points + .iter() + .enumerate() + .map(|(i, p)| [i as f64, *p]) + .filter(|[_, y]| !hide_zero || y.ne(&0.0)) + .step_by(step) + .collect::>(); + + series.into() +} \ No newline at end of file diff --git a/utils/lipsync_preview/src/main.rs b/utils/lipsync_preview/src/main.rs new file mode 100644 index 00000000..33ee0635 --- /dev/null +++ b/utils/lipsync_preview/src/main.rs @@ -0,0 +1,40 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + +use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::io::prelude::*; +use std::path::PathBuf; + +mod app; +use app::*; + +use eframe::{NativeOptions, run_native}; + +fn main() -> Result<(), Box> { + let args: Vec<_> = std::env::args().skip(1).collect(); + + if args.len() < 1 { + println!("lipsync_preview.exe [input_weights_path]"); + return Ok(()); + } + + let mut app = LipsyncApp::default(); + app.load_args(&args)?; + + let ops = NativeOptions { + drag_and_drop_support: true, + // icon_data: Some(icon), + min_window_size: Some([1000., 600.].into()), + follow_system_theme: false, // Always dark by default + default_theme: eframe::Theme::Dark, + ..NativeOptions::default() + }; + + run_native( + "Lipsync Preview", + ops, + Box::new(|_cc| Box::new(app)) + ) + .map_err(|e| e.into()) +} diff --git a/utils/shared/Cargo.toml b/utils/shared/Cargo.toml new file mode 100644 index 00000000..d58a747c --- /dev/null +++ b/utils/shared/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "shared" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +grim = { workspace = true } \ No newline at end of file diff --git a/utils/shared/src/lib.rs b/utils/shared/src/lib.rs new file mode 100644 index 00000000..7ce4e1d8 --- /dev/null +++ b/utils/shared/src/lib.rs @@ -0,0 +1,31 @@ +use std::error::Error; +use std::path::PathBuf; + +use grim::{SystemInfo}; +use grim::io::*; +use grim::scene::{ObjectDir}; + +pub struct MiloLoader { + pub path: PathBuf, + pub sys_info: SystemInfo, + pub obj_dir: ObjectDir, +} + +impl MiloLoader { + pub fn from_path(milo_path: PathBuf) -> Result> { + // Open milo + let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let milo = MiloArchive::from_stream(&mut stream)?; + + // Unpack milo + let system_info = SystemInfo::guess_system_info(&milo, &milo_path); + let mut obj_dir = milo.unpack_directory(&system_info)?; + obj_dir.unpack_entries(&system_info)?; + + Ok(Self { + path: milo_path, + sys_info: system_info, + obj_dir + }) + } +} \ No newline at end of file From 4cf4f7d8ed8e55a5bdafd0d47b910f296dc19a3b Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 2 Mar 2023 18:54:35 -0500 Subject: [PATCH 037/113] Add load char lip sync --- core/grim/src/scene/char_lip_sync/io.rs | 65 ++++++++++++++++++++++ core/grim/src/scene/char_lip_sync/mod.rs | 71 ++++++++++++++++++++++++ core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/object.rs | 4 ++ 4 files changed, 142 insertions(+) create mode 100644 core/grim/src/scene/char_lip_sync/io.rs create mode 100644 core/grim/src/scene/char_lip_sync/mod.rs diff --git a/core/grim/src/scene/char_lip_sync/io.rs b/core/grim/src/scene/char_lip_sync/io.rs new file mode 100644 index 00000000..76618a24 --- /dev/null +++ b/core/grim/src/scene/char_lip_sync/io.rs @@ -0,0 +1,65 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use thiserror::Error as ThisError; +use std::error::Error; + +#[derive(Debug, ThisError)] +pub enum CharLipSyncReadError { + #[error("CharLipSync version of {version} not supported")] + CharLipSyncVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 0 => true, + _ => false + } +} + +impl ObjectReadWrite for CharLipSync { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + + // If not valid, return unsupported error + if !is_version_supported(version) { + // TODO: Switch to custom error + return Err(Box::new(CharLipSyncReadError::CharLipSyncVersionNotSupported { + version + })); + } + + load_object(self, &mut reader, info)?; + + // Read visemes + self.visemes.clear(); + let visemes_count = reader.read_uint32()?; + for _ in 0..visemes_count { + let viseme = reader.read_prefixed_string()?; + self.visemes.push(viseme); + } + + self.frames_count = reader.read_uint32()? as usize; + + // Read keyframe data + let data_size = reader.read_uint32()? as usize; + self.data = reader.read_bytes(data_size)?; + + Ok(()) + } + + fn save(&self, _stream: &mut dyn Stream, _info: &SystemInfo) -> Result<(), Box> { + todo!("Implement save() for CharLipSync") + + /*let mut writer = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 1; + Ok(())*/ + } +} diff --git a/core/grim/src/scene/char_lip_sync/mod.rs b/core/grim/src/scene/char_lip_sync/mod.rs new file mode 100644 index 00000000..5c73d161 --- /dev/null +++ b/core/grim/src/scene/char_lip_sync/mod.rs @@ -0,0 +1,71 @@ +mod io; + +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +#[milo] +pub struct CharLipSync { + pub visemes: Vec, + pub frames_count: usize, + pub data: Vec +} + +impl Default for CharLipSync { + fn default() -> CharLipSync { + CharLipSync { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // CharLipSync object + visemes: Vec::new(), + frames_count: 0, + data: Vec::new() + } + } +} + +pub struct VisemeFrame<'a> { + pub viemes: Vec<(&'a str, u8)>, // Viseme name, weight +} + +impl CharLipSync { + pub fn get_frames<'a>(&'a self) -> Vec { + let mut frames = Vec::new(); + + let mut idx = 0; + let mut data = self.data.iter(); + + // Interpret frames from raw data + while let Some(weight_count) = data.next().map(|d| *d as usize) { + // Check in case data extends beyond frame count + if idx >= self.frames_count { + break; + } + + let mut weights = Vec::new(); + + for _ in 0..weight_count { + let Some(viseme_idx) = data.next().map(|d| *d as usize) else { + break; + }; + + let Some(weight) = data.next().map(|d| *d) else { + break; + }; + + weights.push((self.visemes[viseme_idx].as_str(), weight)); + } + + frames.push(VisemeFrame { + viemes: weights + }); + + idx += 1; + } + + frames + } +} \ No newline at end of file diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index 8d381379..ec35ca4a 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -3,6 +3,7 @@ mod cam; mod char_bones_samples; mod char_clip; mod char_clip_samples; +mod char_lip_sync; mod cube_tex; mod draw; mod group; @@ -27,6 +28,7 @@ pub use cam::*; pub use char_clip::*; pub use char_bones_samples::*; pub use char_clip_samples::*; +pub use char_lip_sync::*; pub use cube_tex::*; pub use draw::*; pub use group::*; diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index 6864d7cc..e801aebc 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -6,6 +6,7 @@ pub enum Object { Anim(AnimObject), Cam(CamObject), CharClipSamples(CharClipSamples), + CharLipSync(CharLipSync), CubeTex(CubeTexObject), Draw(DrawObject), Group(GroupObject), @@ -34,6 +35,7 @@ impl Object { Object::Anim(anim) => &anim.name, Object::Cam(cam) => &cam.name, Object::CharClipSamples(ccs) => &ccs.name, + Object::CharLipSync(cls) => &cls.name, Object::CubeTex(cube) => &cube.name, Object::Draw(draw) => &draw.name, Object::Group(grp) => &grp.name, @@ -55,6 +57,7 @@ impl Object { Object::Anim(_) => "Anim", Object::Cam(_) => "Cam", Object::CharClipSamples(_) => "CharClipSamples", + Object::CharLipSync(_) => "CharLipSync", Object::CubeTex(_) => "CubeTex", Object::Draw(_) => "Draw", Object::Group(_) => "Group", @@ -121,6 +124,7 @@ impl Object { "Anim" => unpack_object(packed, info).map(|o| Object::Anim(o)), "Cam" => unpack_object(packed, info).map(|o| Object::Cam(o)), "CharClipSamples" => unpack_object(packed, info).map(|o| Object::CharClipSamples(o)), + "CharLipSync" => unpack_object(packed, info).map(|o| Object::CharLipSync(o)), "CubeTex" => unpack_object(packed, info).map(|o| Object::CubeTex(o)), "Draw" => unpack_object(packed, info).map(|o| Object::Draw(o)), "Group" => unpack_object(packed, info).map(|o| Object::Group(o)), From ec0d538ebdcca5aaa88c82b1211e4818449ffc7b Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 2 Mar 2023 20:27:19 -0500 Subject: [PATCH 038/113] Add load + save moph --- core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/morph/io.rs | 116 +++++++++++++++++++++++++++++++ core/grim/src/scene/morph/mod.rs | 41 +++++++++++ core/grim/src/scene/object.rs | 5 ++ 4 files changed, 164 insertions(+) create mode 100644 core/grim/src/scene/morph/io.rs create mode 100644 core/grim/src/scene/morph/mod.rs diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index ec35ca4a..9cbd8171 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -13,6 +13,7 @@ mod mesh; mod mesh_anim; mod meta; mod milo; +mod morph; mod object_dir; mod object; mod p9_song_pref; @@ -39,6 +40,7 @@ pub use self::meta::*; pub use self::mesh::*; pub use self::mesh_anim::*; pub use self::milo::*; +pub use self::morph::*; pub use self::object_dir::*; pub use self::object::*; pub use p9_song_pref::*; diff --git a/core/grim/src/scene/morph/io.rs b/core/grim/src/scene/morph/io.rs new file mode 100644 index 00000000..bd08e622 --- /dev/null +++ b/core/grim/src/scene/morph/io.rs @@ -0,0 +1,116 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use thiserror::Error as ThisError; +use std::error::Error; + +#[derive(Debug, ThisError)] +pub enum MorphReadError { + #[error("Morph version of {version} not supported")] + MorphVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 3 => true, + _ => false + } +} + +impl ObjectReadWrite for Morph { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + + // If not valid, return unsupported error + if !is_version_supported(version) { + // TODO: Switch to custom error + return Err(Box::new(MorphReadError::MorphVersionNotSupported { + version + })); + } + + if version > 3 { + load_object(self, &mut reader, info)?; + } + load_anim(self, &mut reader, info, false)?; + + let pose_count = reader.read_uint32()?; + reader.seek(SeekFrom::Current(4))?; // Skip 0 data + + // Read poses + self.poses.clear(); + for _ in 0..pose_count { + let pose = load_morph_pose(&mut reader)?; + self.poses.push(pose); + } + + self.normals = reader.read_boolean()?; + self.spline = reader.read_boolean()?; + self.intensity = reader.read_float32()?; + + Ok(()) + } + + fn save(&self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut writer = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 3; + + if version > 3 { + save_object(self, &mut writer, info)?; + } + save_anim(self, &mut writer, info, false)?; + + writer.write_uint32(self.poses.len() as u32)?; + writer.write_uint32(0)?; // Always 0 + + for pose in self.poses.iter() { + save_morph_pose(pose, &mut writer)?; + } + + writer.write_boolean(self.normals)?; + writer.write_boolean(self.spline)?; + writer.write_float32(self.intensity)?; + + Ok(()) + } +} + +fn load_morph_pose(reader: &mut Box) -> Result> { + let count = reader.read_uint32()?; + reader.seek(SeekFrom::Current(4))?; // Skip 0 data + + let mut keys = Vec::new(); + + for _ in 0..count { + let pos = reader.read_float32()?; + let value = reader.read_float32()?; + + keys.push(AnimEvent { + value, + pos + }) + } + + Ok(MorphPose { + events: keys + }) +} + +fn save_morph_pose(pose: &MorphPose, writer: &mut Box) -> Result<(), Box> { + writer.write_uint32(pose.events.len() as u32)?; + writer.write_uint32(0)?; // Always 0 + + for ev in pose.events.iter() { + writer.write_float32(ev.pos)?; + writer.write_float32(ev.value)?; + } + + Ok(()) +} \ No newline at end of file diff --git a/core/grim/src/scene/morph/mod.rs b/core/grim/src/scene/morph/mod.rs new file mode 100644 index 00000000..c8d83e38 --- /dev/null +++ b/core/grim/src/scene/morph/mod.rs @@ -0,0 +1,41 @@ +mod io; + +use super::AnimEvent; +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +#[milo(Anim)] +pub struct Morph { + pub poses: Vec, + pub normals: bool, + pub spline: bool, + pub intensity: f32, +} + +#[derive(Default)] +pub struct MorphPose { + pub events: Vec> +} + +impl Default for Morph { // RndMorph + fn default() -> Morph { + Morph { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // Anim object + anim_objects: Vec::new(), + frame: 0.0, + rate: AnimRate::default(), + + // Morph object + poses: Vec::new(), + normals: true, + spline: true, + intensity: 1.0 + } + } +} diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index e801aebc..b39ee110 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -13,6 +13,7 @@ pub enum Object { Mat(MatObject), Mesh(MeshObject), MeshAnim(MeshAnim), + Morph(Morph), P9SongPref(P9SongPref), PropAnim(PropAnim), SynthSample(SynthSample), @@ -42,6 +43,7 @@ impl Object { Object::Mat(mat) => &mat.name, Object::Mesh(mesh) => &mesh.name, Object::MeshAnim(mesh_anim) => &mesh_anim.name, + Object::Morph(morph) => &morph.name, Object::P9SongPref(pref) => &pref.name, Object::PropAnim(prop) => &prop.name, Object::SynthSample(synth) => &synth.name, @@ -64,6 +66,7 @@ impl Object { Object::Mat(_) => "Mat", Object::Mesh(_) => "Mesh", Object::MeshAnim(_) => "MeshAnim", + Object::Morph(_) => "Morph", Object::P9SongPref(_) => "P9SongPref", Object::PropAnim(_) => "PropAnim", Object::SynthSample(_) => "SynthSample", @@ -94,6 +97,7 @@ impl Object { Object::Group(obj) => obj, Object::Mat(obj) => obj, Object::Mesh(obj) => obj, + Object::Morph(obj) => obj, Object::P9SongPref(obj) => obj, Object::PropAnim(obj) => obj, Object::Tex(obj) => obj, @@ -131,6 +135,7 @@ impl Object { "Mat" => unpack_object(packed, info).map(|o| Object::Mat(o)), "Mesh" => unpack_object(packed, info).map(|o| Object::Mesh(o)), "MeshAnim" => unpack_object(packed, info).map(|o| Object::MeshAnim(o)), + "Morph" => unpack_object(packed, info).map(|o| Object::Morph(o)), "P9SongPref" => unpack_object(packed, info).map(|o| Object::P9SongPref(o)), "PropAnim" => unpack_object(packed, info).map(|o| Object::PropAnim(o)), "SynthSample" => unpack_object(packed, info).map(|o| Object::SynthSample(o)), From b9eb8b61822ae6d9cfda057e0cc1c15dbee58304 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 2 Mar 2023 20:43:40 -0500 Subject: [PATCH 039/113] Support v4 trans anim --- core/grim/src/scene/trans_anim/io.rs | 53 +++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/core/grim/src/scene/trans_anim/io.rs b/core/grim/src/scene/trans_anim/io.rs index a3bb8e34..66d6af8f 100644 --- a/core/grim/src/scene/trans_anim/io.rs +++ b/core/grim/src/scene/trans_anim/io.rs @@ -15,7 +15,7 @@ pub enum TransAnimReadError { fn is_version_supported(version: u32) -> bool { match version { - 6 | 7 => true, + 4 | 6 | 7 => true, // KRPAI, GH2, TBRB _ => false } } @@ -34,11 +34,39 @@ impl ObjectReadWrite for TransAnim { })); } - if version >= 4 { + if version > 4 { load_object(self, &mut reader, info)?; } load_anim(self, &mut reader, info, false)?; + if version < 6 { + // RndDrawable::DumpLoad() + // Basically just ignore these values... + + let min_version = reader.read_uint32()?; + _ = reader.read_boolean()?; + + if min_version < 2 { + let str_count = reader.read_uint32()?; + for _ in 0..str_count { + _ = reader.read_prefixed_string()?; + } + } + + if min_version > 0 { + // Skip zero data + reader.seek(SeekFrom::Current(16))?; + } + + if min_version > 2 { + reader.seek(SeekFrom::Current(4))?; + } + + if min_version > 3 { + _ = reader.read_prefixed_string()?; + } + } + self.trans_object = reader.read_prefixed_string()?; // Reset keys @@ -93,13 +121,30 @@ impl ObjectReadWrite for TransAnim { let mut writer = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); // TODO: Get version from system info - let version = 7; + let version = if info.version == 10 { 4 } else { 7 }; writer.write_uint32(version)?; - save_object(self, &mut writer, info)?; + if version > 4 { + save_object(self, &mut writer, info)?; + } save_anim(self, &mut writer, info, false)?; + if version < 6 { + const MIN_VERSION: u32 = 1; + + writer.write_uint32(MIN_VERSION)?; + writer.write_boolean(true)?; + + writer.write_uint32(0)?; // No strings + + // 4x ints + writer.write_uint64(0)?; + writer.write_uint64(0)?; + + writer.write_uint32(0)?; // Empty string + } + writer.write_prefixed_string(&self.trans_object)?; // Write rot + trans keys From 16dc7a465ace07846b1604813f7c5b5912c4e11a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 3 Mar 2023 21:41:40 -0500 Subject: [PATCH 040/113] Use version 0 for anims for gh1 milos --- core/grim/src/scene/anim/io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/scene/anim/io.rs b/core/grim/src/scene/anim/io.rs index e0c21fd0..83923010 100644 --- a/core/grim/src/scene/anim/io.rs +++ b/core/grim/src/scene/anim/io.rs @@ -63,7 +63,7 @@ pub(crate) fn load_anim(anim: &mut T, reader: &mut Box, i pub(crate) fn save_anim(anim: &T, writer: &mut Box, info: &SystemInfo, write_meta: bool) -> Result<(), Box> { // TODO: Get version from system info - let version = 4; + let version = if version == 10 { 0 } else { 4 }; writer.write_uint32(version)?; if write_meta { From 1d5b112cecb8164ac41b62fa72b0b26ccdeae5e5 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 3 Mar 2023 21:42:08 -0500 Subject: [PATCH 041/113] Fix anim save --- core/grim/src/scene/anim/io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/scene/anim/io.rs b/core/grim/src/scene/anim/io.rs index 83923010..0e2a8fee 100644 --- a/core/grim/src/scene/anim/io.rs +++ b/core/grim/src/scene/anim/io.rs @@ -63,7 +63,7 @@ pub(crate) fn load_anim(anim: &mut T, reader: &mut Box, i pub(crate) fn save_anim(anim: &T, writer: &mut Box, info: &SystemInfo, write_meta: bool) -> Result<(), Box> { // TODO: Get version from system info - let version = if version == 10 { 0 } else { 4 }; + let version = if info.version == 10 { 0 } else { 4 }; writer.write_uint32(version)?; if write_meta { From c69039abab5b8d8c4f49337f2b3f8cfab17c74f3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 4 Mar 2023 20:43:22 -0500 Subject: [PATCH 042/113] Fix visemes typo --- core/grim/src/scene/char_lip_sync/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/grim/src/scene/char_lip_sync/mod.rs b/core/grim/src/scene/char_lip_sync/mod.rs index 5c73d161..1398a344 100644 --- a/core/grim/src/scene/char_lip_sync/mod.rs +++ b/core/grim/src/scene/char_lip_sync/mod.rs @@ -28,7 +28,7 @@ impl Default for CharLipSync { } pub struct VisemeFrame<'a> { - pub viemes: Vec<(&'a str, u8)>, // Viseme name, weight + pub visemes: Vec<(&'a str, u8)>, // Viseme name, weight } impl CharLipSync { @@ -60,7 +60,7 @@ impl CharLipSync { } frames.push(VisemeFrame { - viemes: weights + visemes: weights }); idx += 1; From 0397735ead0e9ab7c9303f8c65f47add6e284e3e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 4 Mar 2023 21:57:41 -0500 Subject: [PATCH 043/113] Write version for morph save --- core/grim/src/scene/morph/io.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/grim/src/scene/morph/io.rs b/core/grim/src/scene/morph/io.rs index bd08e622..07bef234 100644 --- a/core/grim/src/scene/morph/io.rs +++ b/core/grim/src/scene/morph/io.rs @@ -61,6 +61,7 @@ impl ObjectReadWrite for Morph { // TODO: Get version from system info let version = 3; + writer.write_uint32(version)?; if version > 3 { save_object(self, &mut writer, info)?; From 2a727bdf2455d5849a5b275ae20036960b8ad7d4 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 4 Mar 2023 22:23:31 -0500 Subject: [PATCH 044/113] Fix saving trans anim --- core/grim/src/scene/object.rs | 1 + core/grim/src/scene/trans_anim/io.rs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index b39ee110..3ef07d51 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -102,6 +102,7 @@ impl Object { Object::PropAnim(obj) => obj, Object::Tex(obj) => obj, Object::Trans(obj) => obj, + Object::TransAnim(obj) => obj, _ => todo!("Test"), }; diff --git a/core/grim/src/scene/trans_anim/io.rs b/core/grim/src/scene/trans_anim/io.rs index 66d6af8f..52e40877 100644 --- a/core/grim/src/scene/trans_anim/io.rs +++ b/core/grim/src/scene/trans_anim/io.rs @@ -141,8 +141,6 @@ impl ObjectReadWrite for TransAnim { // 4x ints writer.write_uint64(0)?; writer.write_uint64(0)?; - - writer.write_uint32(0)?; // Empty string } writer.write_prefixed_string(&self.trans_object)?; From 17e6b326f0096fa8e7f2c74de5571701cd614f5a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 4 Mar 2023 22:37:33 -0500 Subject: [PATCH 045/113] Add basic rb lipsync to kr converter --- apps/cli/scene_tool/src/apps/milo2kr.rs | 220 ++++++++++++++++++++++++ apps/cli/scene_tool/src/apps/mod.rs | 5 + 2 files changed, 225 insertions(+) create mode 100644 apps/cli/scene_tool/src/apps/milo2kr.rs diff --git a/apps/cli/scene_tool/src/apps/milo2kr.rs b/apps/cli/scene_tool/src/apps/milo2kr.rs new file mode 100644 index 00000000..bbbdd0c5 --- /dev/null +++ b/apps/cli/scene_tool/src/apps/milo2kr.rs @@ -0,0 +1,220 @@ +use crate::apps::{GameOptions, SubApp}; +use clap::Parser; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::error::Error; +use std::fs; +use std::path::{Path, PathBuf}; +use thiserror::Error; + +use grim::{Platform, SystemInfo}; +use grim::io::*; +use grim::scene::{AnimEvent, CharLipSync, Object, ObjectDir, ObjectDirBase, MiloObject, Morph, MorphPose, PackedObject, Quat, Tex, TransAnim}; + +#[derive(Parser, Debug)] +pub struct Milo2KrApp { + #[arg(help = "Path to input milo file w/ lipsync", required = true)] + pub input_path: String, + #[arg(help = "Path to output rnd file", required = true)] + pub output_path: String, + #[arg(short, long, help = "Enable to leave output milo archive uncompressed", required = false)] + pub uncompressed: bool, +} + +const LIPSYNC_FPS: u32 = 30; + +impl SubApp for Milo2KrApp { + fn process(&mut self) -> Result<(), Box> { + let milo_path = Path::new(&self.input_path); + let rnd_path = Path::new(&self.output_path); + + if let Some(file_name) = milo_path.file_name() { + let file_name = file_name.to_str().unwrap_or("file"); + + println!("Opening {}", file_name); + } + + let mut stream: Box = Box::new(FileStream::from_path_as_read_open(milo_path)?); + let milo = MiloArchive::from_stream(&mut stream)?; + + // TODO: First get system info from args then guess if not supplied + //let system_info = self.get_system_info(); + let system_info = SystemInfo::guess_system_info(&milo, &milo_path); + + let obj_dir = milo.unpack_directory(&system_info)?; + //obj_dir.unpack_entries(&SYSTEM_INFO); + + let lipsyncs = get_lipsync_files(&obj_dir, &system_info); + + if lipsyncs.is_empty() { + println!("No lipsync files found in milo"); + return Ok(()); + } + + let Some(default_lipsync) = lipsyncs.iter().find(|l| l.get_name().eq("song.lipsync")) else { + println!("Can't find song.lipsync in milo"); + return Ok(()); + }; + + let song_length = (default_lipsync.frames_count as f32 / LIPSYNC_FPS as f32) * 1000.; + + let morphs = convert_lipsync_to_morphs(default_lipsync); + + let kr_info = SystemInfo { + platform: Platform::PS2, + version: 10, + endian: IOEndian::Little + }; + + let kr_block_type = if self.uncompressed { BlockType::TypeA } else { BlockType::TypeB }; + + let kr_obj_dir = ObjectDir::ObjectDir(ObjectDirBase { + entries: morphs + .into_iter() + .map(|m| Object::Morph(m)) + .chain(vec![ + Object::TransAnim(TransAnim { + name: String::from("song_head.tnm"), + rot_keys: vec![ + AnimEvent { + value: Quat { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, + pos: if song_length > 1000. { 1000. } else { 0. } + }, + AnimEvent { + value: Quat { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }, + pos: (song_length - 1000.).max(0.0) + } + ], + trans_anim_owner: String::from("song_head.tnm"), + trans_spline: true, + ..Default::default() + }) + ]) + .collect(), + ..ObjectDirBase::new() + }); + + let rnd = MiloArchive::from_object_dir(&kr_obj_dir, &kr_info, Some(kr_block_type)).unwrap(); + + let mut stream = FileStream::from_path_as_read_write_create(&rnd_path)?; + rnd.write_to_stream(&mut stream)?; + + if let Some(file_name) = rnd_path.file_name() { + let file_name = file_name.to_str().unwrap(); + println!("Successfully wrote {}", file_name); + } + + Ok(()) + } +} + +fn get_lipsync_files(obj_dir: &ObjectDir, info: &SystemInfo) -> Vec { + obj_dir + .get_entries() + .iter() + .filter_map(|e| match e.get_type() { + "CharLipSync" => match e.unpack(info) { + Some(Object::CharLipSync(cls)) => Some(cls), + _ => panic!("Unable to open {}", e.get_name()) + }, + _ => None + }) + .collect() +} + +fn convert_lipsync_to_morphs(lipsync: &CharLipSync) -> Vec { + // Group changes by pose/viseme name and calculate ms positions + let viseme_weights = lipsync + .get_frames() + .into_iter() + .enumerate() + .fold(HashMap::new(), |mut acc, (idx, frame)| { + //let pos = (idx as f32 / LIPSYNC_FPS as f32) * 1000.0; // Convert to ms + + for (viseme, weight) in frame.visemes { + //let weight = weight as f32 / 255.0; + + acc + .entry(viseme) + .and_modify(|e: &mut Vec<(usize, u8)>| e.push((idx, weight))) + .or_insert_with(|| vec![(idx, weight)]); + } + + acc + }); + + let mouth_pose_map: [(&str, Vec<&str>); 15] = [ + ("EAT", vec![ "Eat_hi", "Eat_lo" ]), + ("ERTH", vec![ "Earth_hi", "Earth_lo" ]), + ("IF", vec![ "If_hi", "If_lo" ]), + ("OX", vec![ "Ox_hi", "Ox_lo" ]), + ("OAT", vec![ "Oat_hi", "Oat_lo" ]), + + ("WET", vec![ "Wet_hi", "Wet_lo" ]), + ("SIZE", vec![ "Size_hi", "Size_lo" ]), + ("CHUR", vec![ "Church_hi", "Church_lo" ]), + ("FAVE", vec![ "Fave_hi", "Fave_lo" ]), + ("THOU", vec![ "Though_hi", "Though_lo" ]), + + ("TOLD", vec![ "Told_hi", "Told_lo" ]), + ("BUMP", vec![ "Bump_hi", "Bump_lo" ]), + ("NEW", vec![ "New_hi", "New_lo" ]), + ("ROAR", vec![ "Roar_hi", "Roar_lo" ]), + ("CAGE", vec![ "Cage_hi", "Cage_lo" ]), + ]; + + /*let brow_map: [(&str, Vec<&str>); 1] = [ + ("EBRR EBRL", vec![ ]), + ];*/ + + vec![ + Morph { + name: String::from("song_brow.mrf"), + poses: vec![ + MorphPose::default(), + MorphPose::default() + ], + ..Morph::default() + }, + Morph { + name: String::from("song_lid.mrf"), + poses: vec![ + MorphPose::default(), + MorphPose::default() + ], + ..Morph::default() + }, + Morph { + name: String::from("song_mouth.mrf"), + poses: convert_visemes_to_poses(&viseme_weights, &mouth_pose_map), + ..Morph::default() + }, + ] +} + +fn convert_visemes_to_poses(viseme_weights: &HashMap<&str, Vec<(usize, u8)>>, pose_map: &[(&str, Vec<&str>)]) -> Vec { + let mut poses = vec![ MorphPose::default() ]; // First one is always empty + + for (_, viseme_names) in pose_map { + // TODO: Average weights between hi + lo values + let weights = viseme_names + .iter() + .filter_map(|v| viseme_weights.get(v)) + .map(|v| v + .iter() + .map(|(i, w)| AnimEvent { + value: *w as f32 / 255.0, + pos: (*i as f32 / LIPSYNC_FPS as f32) * 1000.0 + }) //((*i as f32 / LIPSYNC_FPS as f32) * 1000.0, *w as f32 / 255.0)) + .collect() + ) + .next() + .unwrap_or_else(|| Vec::new()); + + poses.push(MorphPose { + events: weights + }); + } + + poses +} \ No newline at end of file diff --git a/apps/cli/scene_tool/src/apps/mod.rs b/apps/cli/scene_tool/src/apps/mod.rs index f6794c49..2c27748b 100644 --- a/apps/cli/scene_tool/src/apps/mod.rs +++ b/apps/cli/scene_tool/src/apps/mod.rs @@ -5,9 +5,11 @@ use grim::SystemInfo; mod dir2milo; mod milo2dir; +mod milo2kr; mod savemilo; pub use self::dir2milo::*; pub use self::milo2dir::*; +pub use self::milo2kr::*; pub use self::savemilo::*; // From Cargo.toml @@ -31,6 +33,8 @@ enum SubCommand { Dir2Milo(Dir2MiloApp), #[command(name = "milo2dir", about = "Extracts content of milo scene to directory")] Milo2Dir(Milo2DirApp), + #[command(name = "milo2kr", about = "Converts RB milo lipsync to KR rnd")] + Milo2Kr(Milo2KrApp), #[command(name = "savemilo", about = "Save milo")] SaveMilo(SaveMiloApp), } @@ -51,6 +55,7 @@ impl SceneTool { match &mut self.options.commands { SubCommand::Dir2Milo(app) => app.process(), SubCommand::Milo2Dir(app) => app.process(), + SubCommand::Milo2Kr(app) => app.process(), SubCommand::SaveMilo(app) => app.process(), } } From 56a94d30bb72f053e36760814f0af8fc6555e280 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 7 Mar 2023 20:38:24 -0500 Subject: [PATCH 046/113] Use midi tick position for kr lipsync positions --- apps/cli/scene_tool/src/apps/milo2kr.rs | 48 +++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/cli/scene_tool/src/apps/milo2kr.rs b/apps/cli/scene_tool/src/apps/milo2kr.rs index bbbdd0c5..272e6238 100644 --- a/apps/cli/scene_tool/src/apps/milo2kr.rs +++ b/apps/cli/scene_tool/src/apps/milo2kr.rs @@ -9,6 +9,7 @@ use thiserror::Error; use grim::{Platform, SystemInfo}; use grim::io::*; +use grim::midi::{MidiEvent, MidiTextType, MidiFile, MidiTempo, MidiText, MidiTrack}; use grim::scene::{AnimEvent, CharLipSync, Object, ObjectDir, ObjectDirBase, MiloObject, Morph, MorphPose, PackedObject, Quat, Tex, TransAnim}; #[derive(Parser, Debug)] @@ -17,6 +18,8 @@ pub struct Milo2KrApp { pub input_path: String, #[arg(help = "Path to output rnd file", required = true)] pub output_path: String, + #[arg(short = 'm', long, help = "Base MIDI file", required = true)] + pub midi: String, #[arg(short, long, help = "Enable to leave output milo archive uncompressed", required = false)] pub uncompressed: bool, } @@ -28,6 +31,12 @@ impl SubApp for Milo2KrApp { let milo_path = Path::new(&self.input_path); let rnd_path = Path::new(&self.output_path); + // Open midi + let Some(mid) = MidiFile::from_path(&self.midi) else { + println!("Unable to open midi file"); + return Ok(()); + }; + if let Some(file_name) = milo_path.file_name() { let file_name = file_name.to_str().unwrap_or("file"); @@ -58,7 +67,40 @@ impl SubApp for Milo2KrApp { let song_length = (default_lipsync.frames_count as f32 / LIPSYNC_FPS as f32) * 1000.; - let morphs = convert_lipsync_to_morphs(default_lipsync); + let mut morphs = convert_lipsync_to_morphs(default_lipsync); + + // Update realtime positions to midi ticks + // TODO: Move to same method + for morph in morphs.iter_mut() { + for pose in morph.poses.iter_mut() { + let mut tempos = mid.tempo.iter().rev(); + let tpq = mid.ticks_per_quarter; + + let mut update_tempo = || { + if let Some(tempo) = tempos.next() { + (tempo.pos_realtime.unwrap(), tempo.pos, tempo.mpq) + } else { + (0.0, 0, 60_000_000 / 120) + } + }; + + let (mut curr_pos_ms, mut curr_pos_ticks, mut curr_mpq) = update_tempo(); + + for ev in pose.events.iter_mut().rev() { + // Update current tempo + while (ev.pos as f64) < curr_pos_ms { + (curr_pos_ms, curr_pos_ticks, curr_mpq) = update_tempo(); + } + + // Calculate delta ticks + let delta_ms = (ev.pos as f64) - curr_pos_ms; + let delta_ticks = (1000.0 * (tpq as f64) * delta_ms) / (curr_mpq as f64); + + let tick_pos = (curr_pos_ticks as f64) + delta_ticks; + ev.pos = tick_pos as f32; + } + } + } let kr_info = SystemInfo { platform: Platform::PS2, @@ -197,13 +239,15 @@ fn convert_visemes_to_poses(viseme_weights: &HashMap<&str, Vec<(usize, u8)>>, po for (_, viseme_names) in pose_map { // TODO: Average weights between hi + lo values + // TODO: Only add new event if value changes let weights = viseme_names .iter() + .skip(1) .filter_map(|v| viseme_weights.get(v)) .map(|v| v .iter() .map(|(i, w)| AnimEvent { - value: *w as f32 / 255.0, + value: (*w as f32 / 255.0) * 1.2814, // Max seems to be 199. This effectively increases to 255. pos: (*i as f32 / LIPSYNC_FPS as f32) * 1000.0 }) //((*i as f32 / LIPSYNC_FPS as f32) * 1000.0, *w as f32 / 255.0)) .collect() From f3cfb1222d98fc029cb0cccc7c5f513a24d33101 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Mar 2023 18:57:22 -0400 Subject: [PATCH 047/113] Parse blink event (disabled for now) --- apps/cli/scene_tool/src/apps/milo2kr.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/cli/scene_tool/src/apps/milo2kr.rs b/apps/cli/scene_tool/src/apps/milo2kr.rs index 272e6238..020a4652 100644 --- a/apps/cli/scene_tool/src/apps/milo2kr.rs +++ b/apps/cli/scene_tool/src/apps/milo2kr.rs @@ -220,6 +220,7 @@ fn convert_lipsync_to_morphs(lipsync: &CharLipSync) -> Vec { }, Morph { name: String::from("song_lid.mrf"), + //poses: convert_blink_visemes_to_lid_poses(&viseme_weights), poses: vec![ MorphPose::default(), MorphPose::default() @@ -234,6 +235,24 @@ fn convert_lipsync_to_morphs(lipsync: &CharLipSync) -> Vec { ] } +fn convert_blink_visemes_to_lid_poses(viseme_weights: &HashMap<&str, Vec<(usize, u8)>>) -> Vec { + vec![ + MorphPose::default(), // First one is always empty + MorphPose { + events: match viseme_weights.get("Blink") { + Some(blink) => blink + .iter() + .map(|(i, w)| AnimEvent { + value: (*w as f32 / 255.0), + pos: (*i as f32 / LIPSYNC_FPS as f32) * 1000.0 + }) + .collect(), + _ => Vec::new() // TODO: Generate blink events + } + } + ] +} + fn convert_visemes_to_poses(viseme_weights: &HashMap<&str, Vec<(usize, u8)>>, pose_map: &[(&str, Vec<&str>)]) -> Vec { let mut poses = vec![ MorphPose::default() ]; // First one is always empty From 521bfb4ab38aa7f874d502337ad07fcad564bb90 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 21 Mar 2023 19:01:10 -0400 Subject: [PATCH 048/113] Update map_objects for gltf export --- core/grim/src/model/export.rs | 98 ++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 4849cdd1..df611055 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -425,7 +425,7 @@ impl GltfExporter { self.dirs_rc.clear(); - for mut dir_entry in self.object_dirs.drain(..) { + for (i, mut dir_entry) in self.object_dirs.drain(..).enumerate() { let entries = dir_entry.entries.drain(..).collect::>(); let parent = Rc::new(dir_entry); @@ -499,6 +499,80 @@ impl GltfExporter { self.dirs_rc.push(parent); } + + // Hacky way to default parent to fix skeleton + // Find first parent containing bones + let parent_skeleton = self.transforms + .values() + .map(|t| (&t.parent, true)) + .chain(self.meshes.values().map(|m| (&m.parent, is_mesh_joint(m)))) + .find_map(|t| match t { + (parent, true) => Some(parent.clone()), + _ => None + }); + + let Some(parent_skeleton) = parent_skeleton else { + return; + }; + + // Update meshes with bones to reference parent skeleton + /*self.meshes + .values_mut() + .filter(|m| match m { + (parent, _, true) if m.as_ref().path.ne(&parent_skeleton.as_ref().path) => { + todo!() + }, + _ => todo!() + });*/ + + let mut new_children = HashSet::new(); + + for (_, m) in self.meshes.iter_mut() { + // Ignore meshes in skeleton dir + if m.parent.as_ref().path.eq(&parent_skeleton.as_ref().path) { + continue; + } + + if m.object.bones.iter().any(|b| !b.name.is_empty()) { + // Update mesh parent if at least one bone found + /*m.object.parent = match &parent_skeleton.dir { + ObjectDir::ObjectDir(base) => base.name.to_owned(), + };*/ + + let dir_name = match &m.parent.dir { + ObjectDir::ObjectDir(base) => &base.name, + }; + + if !new_children.contains(dir_name) { + new_children.insert(dir_name.to_owned()); + } + } + } + + // Add empty trans objects to link children to parent skeleton + for child_name in new_children.drain() { + self.transforms.insert(child_name.to_owned(), MappedObject { + parent: parent_skeleton.clone(), + object: TransObject { + name: child_name, + parent: match &parent_skeleton.dir { + ObjectDir::ObjectDir(base) => base.name.to_owned(), + }, + ..Default::default() + } + }); + } + + /*let parent_skeleton = self.transforms + .values() + .map(|t| (&t.parent, &t.object as &dyn Trans, true)) + .chain(self.meshes.values().map(|m| (&m.parent, &m.object as &dyn Trans, is_mesh_joint(m)))) + .filter(|t| match t { + (parent, _, true) if parent.as_ref().path.ne(&self.dirs_rc[0].as_ref().path) => { + todo!() + }, + _ => todo!() + });*/ } fn get_transform<'a>(&'a self, name: &str) -> Option<&'a dyn Trans> { @@ -751,6 +825,7 @@ impl GltfExporter { .nodes .iter() .map(|n| n.value()) + .filter(|_| false) .collect::>(); let mut skins = Vec::new(); @@ -758,7 +833,7 @@ impl GltfExporter { for (i, idx) in root_indices.into_iter().enumerate() { let mut joints = Vec::new(); - self.find_joints(gltf, idx, &mut joints, na::Matrix4::identity()); + self.find_joints(gltf, idx, &mut joints, na::Matrix4::identity(), 0); if !joints.is_empty() { // TODO: Figure out how to handle when nested @@ -800,7 +875,7 @@ impl GltfExporter { bone_indices } - fn find_joints(&self, gltf: &json::Root, idx: usize, joints: &mut Vec<(usize, na::Matrix4)>, parent_mat: na::Matrix4) { + fn find_joints(&self, gltf: &json::Root, idx: usize, joints: &mut Vec<(usize, na::Matrix4)>, parent_mat: na::Matrix4, depth: usize) { let (node_name, children, mat) = gltf .nodes .get(idx) @@ -842,9 +917,20 @@ impl GltfExporter { || self.meshes.get(node_name.as_str()).map(is_mesh_joint).unwrap_or_default(); if is_joint { - // Calculate inverse bind matrix (shouldn't fail but idk) + // Calculate inverse bind matrix (shouldn't fail) // Also convert to gl space - let mut ibm = mat.try_inverse().unwrap_or_default() * super::MILOSPACE_TO_GLSPACE; + /*let mut ibm = if depth > 0 { + (mat * super::MILOSPACE_TO_GLSPACE).try_inverse().unwrap_or_default() + } else { + mat.try_inverse().unwrap_or_default() + };*/ + + let mut ibm = mat.try_inverse().unwrap_or_default(); + + /*if depth == 0 { + ibm *= super::MILOSPACE_TO_GLSPACE + }*/ + ibm[15] = 1.0; // Force for precision // Add index to joint list @@ -854,7 +940,7 @@ impl GltfExporter { if let Some(children) = children { // Traverse children for child in children { - self.find_joints(gltf, child.value(), joints, mat); + self.find_joints(gltf, child.value(), joints, mat, depth + 1); } } } From 69f81e29b1ca88f07ada39de0986844f949fa8b3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 11 Apr 2023 19:32:47 -0400 Subject: [PATCH 049/113] Update rerun and nalgebra --- utils/anim_preview/Cargo.toml | 4 ++-- utils/anim_preview/src/main.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index 481c7010..f6c6def8 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -7,6 +7,6 @@ edition.workspace = true [dependencies] grim = { workspace = true } keyframe = "1.1.1" -nalgebra = "0.32.1" -rerun = "0.2.0" +nalgebra = "0.32.2" +rerun = "0.4.0" shared = { path = "../shared" } \ No newline at end of file diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index d8af1bfe..eba8e32f 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -18,7 +18,7 @@ use rerun::external::glam; use rerun::{ coordinates::{Handedness, SignedAxis3}, components::{Arrow3D, ColorRGBA, LineStrip3D, MeshId, Point3D, Quaternion, Radius, RawMesh3D, Rigid3, Scalar, TextEntry, Transform, Vec3D, ViewCoordinates}, - MsgSender, Session, + MsgSender, Session, SessionBuilder, time::Timeline }; @@ -78,7 +78,7 @@ fn main() -> Result<(), Box> { }) .collect::>(); - let mut session = Session::new(); + let mut session = SessionBuilder::new("anim_preview").buffered(); MsgSender::new(format!("world")) /*.with_component(&[ @@ -272,7 +272,7 @@ fn main() -> Result<(), Box> { } } - session.show().unwrap(); + rerun::native_viewer::show(&session).unwrap(); Ok(()) } From e19f02dd81f72b086c799d173b14a4a410a41061 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 11 Apr 2023 19:43:35 -0400 Subject: [PATCH 050/113] Update nalgebra in lipsync_preview --- utils/lipsync_preview/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/lipsync_preview/Cargo.toml b/utils/lipsync_preview/Cargo.toml index 5db0833f..9c890089 100644 --- a/utils/lipsync_preview/Cargo.toml +++ b/utils/lipsync_preview/Cargo.toml @@ -8,6 +8,6 @@ edition.workspace = true grim = { workspace = true, features = [ "audio" ] } eframe = "0.21.3" keyframe = "1.1.1" -nalgebra = "0.32.1" +nalgebra = "0.32.2" #rerun = "0.2.0" shared = { path = "../shared" } \ No newline at end of file From 1e636bf2bacc4303a735f07d502d16b2ba0bb99c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 2 Jun 2023 19:28:10 -0400 Subject: [PATCH 051/113] Support reading amp dtb --- core/grim/src/dta/io.rs | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/core/grim/src/dta/io.rs b/core/grim/src/dta/io.rs index 25eaadcf..698042c7 100644 --- a/core/grim/src/dta/io.rs +++ b/core/grim/src/dta/io.rs @@ -3,6 +3,7 @@ use crate::dta::*; use itertools::Itertools; use thiserror::Error as ThisError; use std::error::Error; +use std::hint::unreachable_unchecked; #[derive(Debug, ThisError)] pub enum DtaLoadError { @@ -10,6 +11,17 @@ pub enum DtaLoadError { UnknownNodeType { node_type: u32 }, + #[error("Unknown dtb version: {version:#02X}")] + UnknownVersion { + version: u32 + }, +} + +#[derive(Debug)] +pub enum DataArrayIOSettings { + Milo, + Forge, + Amplitude, } impl DataArray { @@ -39,8 +51,32 @@ impl RootData { let has_data = stream.read_boolean()?; if has_data { self.data = load_array(stream)?; + }; + + Ok(()) + } + + pub fn load_with_settings(&mut self, stream: &mut Box, settings: DataArrayIOSettings) -> Result<(), Box> { + // Clear data + self.data.clear(); + + // Read data + let data_version = stream.read_uint8()?; + + if data_version != 2 { + return Err(Box::new(DtaLoadError::UnknownVersion { + version: data_version as u32 + })); } + // Read original file names (ignore for now) + let name_count = stream.read_uint32()?; + for _ in 0..name_count { + stream.read_prefixed_string()?; + } + + self.data = load_array_amp(stream)?; + Ok(()) } } @@ -59,6 +95,37 @@ pub(crate) fn save_array(data: &Vec, stream: &mut Box, Ok(()) } +pub(crate) fn load_array_amp(stream: &mut Box) -> Result, Box> { + let count = stream.read_uint16()? as usize; + let _id_1 = stream.read_uint32()?; + let _id_2 = stream.read_uint32()?; + + // Types are packed in 2-bits, so 16 types per 32-bit word + let mut type_count = count / 16; + if (count % 16) > 0 { + type_count += 1; + } + + let mut types = vec![0u32; type_count]; + + for typ in types.iter_mut() { + *typ = stream.read_uint32()?; + } + + let mut nodes = Vec::new(); + + for i in 0..count { + // Interpret type + let div = i / 16; + let rem = i % 16; + let typ = (types[div] >> (rem * 2)) & 0x03; + + nodes.push(load_node_amp(stream, typ)?); + } + + Ok(nodes) +} + pub(crate) fn load_array(stream: &mut Box) -> Result, Box> { let count = stream.read_uint16()? as usize; let _id = stream.read_uint32()?; @@ -147,6 +214,18 @@ fn load_node(stream: &mut Box) -> Result Ok(node) } +fn load_node_amp(stream: &mut Box, node_type: u32) -> Result> { + let node = match node_type { + 0x00 => DataArray::Integer(stream.read_int32()?), + 0x01 => DataArray::String(load_string(stream)?), + 0x02 => DataArray::Float(stream.read_float32()?), + 0x03 => DataArray::Array(load_array_amp(stream)?), + _ => unreachable!("Shouldn't be reached. Node type of \"{node_type}\" is invalid"), + }; + + Ok(node) +} + fn save_string(str: &DataString, stream: &mut Box) -> Result<(), Box> { let raw_data = str.get_raw(); From 665b745395e265e9ca0a91d45e7ef371e2bc164e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 2 Jun 2023 20:43:07 -0400 Subject: [PATCH 052/113] Support reading both milo + amplitude dtb in load with settings --- core/grim/src/dta/io.rs | 47 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/core/grim/src/dta/io.rs b/core/grim/src/dta/io.rs index 698042c7..912e6f8b 100644 --- a/core/grim/src/dta/io.rs +++ b/core/grim/src/dta/io.rs @@ -57,27 +57,34 @@ impl RootData { } pub fn load_with_settings(&mut self, stream: &mut Box, settings: DataArrayIOSettings) -> Result<(), Box> { - // Clear data - self.data.clear(); - - // Read data - let data_version = stream.read_uint8()?; - - if data_version != 2 { - return Err(Box::new(DtaLoadError::UnknownVersion { - version: data_version as u32 - })); + match settings { + DataArrayIOSettings::Milo => { + return self.load(stream); + }, + DataArrayIOSettings::Forge => todo!("Can't read Forge dtb"), + DataArrayIOSettings::Amplitude => { + // Clear data + self.data.clear(); + + // Read data + let data_version = stream.read_uint8()?; + + if data_version != 2 { + return Err(Box::new(DtaLoadError::UnknownVersion { + version: data_version as u32 + })); + } + + // Read original file names (ignore for now) + let name_count = stream.read_uint32()?; + for _ in 0..name_count { + stream.read_prefixed_string()?; + } + + self.data = load_array_amp(stream)?; + Ok(()) + } } - - // Read original file names (ignore for now) - let name_count = stream.read_uint32()?; - for _ in 0..name_count { - stream.read_prefixed_string()?; - } - - self.data = load_array_amp(stream)?; - - Ok(()) } } From cc123c1861235dc3adbc3b58cbdc37c5d172ffa7 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 2 Jun 2023 21:54:32 -0400 Subject: [PATCH 053/113] Add dta methods for finding value and type coercion --- core/grim/src/dta/mod.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index ad02c020..aac35e28 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -94,6 +94,51 @@ impl DataArray { DataArray::Undef(_) => 0x25, } } + + pub fn find_value_for_symbol<'a, 'b, T: Into<&'b DataString>>(&'a self, symbol: T) -> Option<&[DataArray]> { + let DataArray::Array(array) = self else { + return None; + }; + + let symbol = symbol.into(); + + match array.split_first() { + // Found element + Some((DataArray::Symbol(s) | DataArray::String(s), elements)) if s.eq(symbol) => { + // Return remaining elements + return Some(elements); + }, + // Keep searching + _ => {} + } + + // Recursively search + for val in array { + let result = val.find_value_for_symbol(symbol); + + if result.is_some() { + return result; + } + } + + None + } + + pub fn as_float(&self) -> Option { + match self { + DataArray::Float(f) => Some(*f), + DataArray::Integer(i) => Some(*i as f32), + _ => None + } + } + + pub fn as_integer(&self) -> Option { + match self { + DataArray::Integer(i) => Some(*i), + DataArray::Float(f) => Some(*f as i32), + _ => None + } + } } #[derive(Debug, Default)] From 63d263e4cb188ebcf2c71d49f1846e77d46b8c9c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 3 Jun 2023 11:39:32 -0400 Subject: [PATCH 054/113] Add as_string for dta --- core/grim/src/dta/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index aac35e28..ea6cffb2 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -139,6 +139,22 @@ impl DataArray { _ => None } } + + pub fn as_string(&self) -> Option<&DataString> { + match self { + DataArray::Variable(s) + | DataArray::Object(s) + | DataArray::Symbol(s) + | DataArray::IfDef(s) + | DataArray::String(s) + | DataArray::Define(s) + | DataArray::Include(s) + | DataArray::Merge(s) + | DataArray::IfNDef(s) + | DataArray::Undef(s) => Some(s), + _ => None + } + } } #[derive(Debug, Default)] From 44fce286fbd2de6a5ed1e1d24c8b3a5460f0adae Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 3 Jun 2023 20:08:16 -0400 Subject: [PATCH 055/113] Support reading amplitude ark --- core/grim/src/ark/io.rs | 132 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/core/grim/src/ark/io.rs b/core/grim/src/ark/io.rs index 87c1d318..6b35b0c8 100644 --- a/core/grim/src/ark/io.rs +++ b/core/grim/src/ark/io.rs @@ -1,15 +1,42 @@ use crate::ark::*; use crate::io::*; use std::collections::HashMap; +use std::io::Read; use std::path::Path; #[cfg(feature = "python")] use pyo3::prelude::*; const MAX_HDR_SIZE: u64 = 20 * 0x100000; // 20MB +const FREQ_ARK_VERSION: i32 = i32::from_le_bytes(*b"ARK\0"); impl Ark { pub fn from_path>(path: T) -> Result { let path = path.as_ref(); + // Check if extension is .ark + let is_ark = path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or_default() + .eq_ignore_ascii_case("ark"); + + if is_ark { + let (version_major, _version_minor) = peek_ark_version(path)?; + + if version_major == FREQ_ARK_VERSION { + todo!("Arks from frequency is not supported") + } + + let mut ark = Ark { + version: version_major, + encryption: ArkEncryption::None, + path: path.to_owned(), + ..Default::default() + }; + + ark.parse_amp_ark()?; + return Ok(ark); + } + let hdr_size = get_file_size(path); if hdr_size > MAX_HDR_SIZE { return Err(ArkReadError::HdrTooBig); @@ -53,6 +80,82 @@ impl Ark { Ok(ark) } + fn parse_amp_ark(&mut self) -> Result<(), ArkReadError> { + let mut stream = FileStream::from_path_as_read_open(&self.path) + .map_err(|_| ArkReadError::ArkNotSupported)?; + let mut reader = BinaryStream::from_stream(&mut stream); + + reader.seek(SeekFrom::Start(4)).unwrap(); + let entry_count = reader.read_uint32().unwrap(); + + // Read strings + reader.seek(SeekFrom::Current((20 * entry_count) as i64)).unwrap(); + + let strings: Vec = if self.version == 1 { + // Read size-prefixed strings from OPM amp demo + let string_count = reader.read_uint32() + .map_err(|_| ArkReadError::ArkNotSupported)?; + + let mut strings = vec![String::new(); string_count as usize]; + + for s in strings.iter_mut() { + *s = reader.read_prefixed_string() + .map_err(|_| ArkReadError::ArkNotSupported)?; + } + + strings + } else { + // Read strings from blob + string entries + let mut strings = parse_string_blob(&mut reader)?; + let string_indicies = parse_string_indices(&mut reader)?; + + string_indicies + .into_iter() + .map(|si| strings + .remove(&si) + .unwrap_or_default()) + .collect() + }; + + // Read entries + reader.seek(SeekFrom::Start(4)).unwrap(); + for id in 0..entry_count { + let mut offset = 0; + + if self.version != 1 { + offset = reader.read_uint32() + .map_err(|_| ArkReadError::ArkNotSupported)?; + } + + let file_name_idx = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; + let dir_path_idx = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; + + if self.version == 1 { + offset = reader.read_uint32() + .map_err(|_| ArkReadError::ArkNotSupported)?; + } + + let size = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; + let inflated_size = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; + + let file_name = &strings[file_name_idx]; + let dir_path = &strings[dir_path_idx]; + + self.entries.push(ArkOffsetEntry { + id, + path: create_full_path(dir_path, file_name), + offset: offset as u64, + part: 0, + size, + inflated_size, + }); + } + + self.sort_entries_by_name(); + + Ok(()) + } + fn parse_header(&mut self, hdr: &[u8]) -> Result<(), ArkReadError> { let mut stream = MemoryStream::from_slice_as_read(hdr); let mut reader = BinaryStream::from_stream(&mut stream); @@ -205,4 +308,33 @@ fn get_ark_part_and_offset(offset: u64, part_size_ranges: &[(u64, u64)]) -> (u32 .find(|(_, (start, end))| &offset >= start && &offset < end) .map(|(i, (start, _))| (i as u32, &offset - start)) .unwrap() +} + +fn peek_ark_version(path: &Path) -> Result<(i32, Option), ArkReadError> { + let mut ark_file = std::fs::File::open(path) + .map_err(|_| ArkReadError::ArkNotSupported)?; + + let v1 = { + // Read ark version + let mut buffer = [0u8; 4]; + + ark_file.read_exact(&mut buffer) + .map_err(|_| ArkReadError::ArkNotSupported)?; + + i32::from_le_bytes(buffer) + }; + + let v2 = if v1 == FREQ_ARK_VERSION { + // Read minor version from freq ark + let mut buffer = [0u8; 4]; + + ark_file.read_exact(&mut buffer) + .map_err(|_| ArkReadError::ArkNotSupported)?; + + Some(i32::from_le_bytes(buffer)) + } else { + None + }; + + Ok((v1, v2)) } \ No newline at end of file From 68efb7f606fd6d08460c2fa97a963ae369883c9d Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 3 Jun 2023 23:29:50 -0400 Subject: [PATCH 056/113] Read ark entry stream and check if gen file --- core/grim/src/ark/ark.rs | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/core/grim/src/ark/ark.rs b/core/grim/src/ark/ark.rs index 07a81277..9e896334 100644 --- a/core/grim/src/ark/ark.rs +++ b/core/grim/src/ark/ark.rs @@ -1,5 +1,5 @@ #[cfg(feature = "python")] use pyo3::prelude::*; -use std::path::PathBuf; +use std::{path::PathBuf, todo}; #[derive(Debug, Default)] #[cfg_attr(feature = "python", pyclass)] @@ -29,6 +29,25 @@ pub struct ArkOffsetEntry { #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub inflated_size: usize } +impl ArkOffsetEntry { + pub fn is_gen_file(&self) -> bool { + if !self.path.contains('/') { + return false; + } + + // Check last directory name for "gen" (there's gotta be a cleaner way to do this) + self.path + .split("/") + .collect::>() + .into_iter() + .rev() + .skip(1) // Skip file name + .next() + .map(|d| d.eq_ignore_ascii_case("gen")) + .unwrap_or_default() + } +} + impl Default for ArkEncryption { fn default() -> Self { ArkEncryption::None @@ -55,4 +74,24 @@ impl Ark { Ok(key) } +} + +impl Ark { + pub fn get_stream(&self, id: u32) -> Result, std::io::Error> { + use std::io::{Read, Seek, SeekFrom}; + + let entry = self + .entries + .iter() + .find(|e| e.id == id) + .expect("Invalid id"); + + // TODO: Support reading from ark parts + let mut file = std::fs::File::open(&self.path)?; + file.seek(SeekFrom::Start(entry.offset))?; + + let mut buffer = vec![0u8; entry.size]; + file.read_exact(&mut buffer)?; + Ok(buffer) + } } \ No newline at end of file From efbab55aa021717367fd2be13fd1fe1a50cb01ef Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 4 Jun 2023 20:08:08 -0400 Subject: [PATCH 057/113] Throw error if ark version is not 1 or 2 --- core/grim/src/ark/io.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/grim/src/ark/io.rs b/core/grim/src/ark/io.rs index 6b35b0c8..f069962a 100644 --- a/core/grim/src/ark/io.rs +++ b/core/grim/src/ark/io.rs @@ -24,6 +24,10 @@ impl Ark { if version_major == FREQ_ARK_VERSION { todo!("Arks from frequency is not supported") + } else if ![1, 2].contains(&version_major) { + return Err(ArkReadError::ArkVersionNotSupported { + version: version_major + }); } let mut ark = Ark { From edcf2535f5f4ecfef79029f17170de8c20dfd25f Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 4 Jun 2023 21:50:20 -0400 Subject: [PATCH 058/113] Switch amp string to symbol --- core/grim/src/dta/io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/dta/io.rs b/core/grim/src/dta/io.rs index 912e6f8b..18847fc9 100644 --- a/core/grim/src/dta/io.rs +++ b/core/grim/src/dta/io.rs @@ -224,7 +224,7 @@ fn load_node(stream: &mut Box) -> Result fn load_node_amp(stream: &mut Box, node_type: u32) -> Result> { let node = match node_type { 0x00 => DataArray::Integer(stream.read_int32()?), - 0x01 => DataArray::String(load_string(stream)?), + 0x01 => DataArray::Symbol(load_string(stream)?), 0x02 => DataArray::Float(stream.read_float32()?), 0x03 => DataArray::Array(load_array_amp(stream)?), _ => unreachable!("Shouldn't be reached. Node type of \"{node_type}\" is invalid"), From 7df1ab5cbb0ed6d788fb20b904f1a59b83ba9be3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 5 Jun 2023 22:34:04 -0400 Subject: [PATCH 059/113] Basic print to dta --- core/grim/src/dta/mod.rs | 213 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index ea6cffb2..e1643334 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -2,10 +2,29 @@ mod errors; mod io; mod parser; +use std::todo; + pub use errors::*; pub use io::*; use parser::*; +#[derive(Debug)] +pub struct DTAFormat { + pub use_quoted_symbols: bool, // Ex: 'shortsongname' + pub indent_char: char, + pub indent_char_count: u8, +} + +impl Default for DTAFormat { + fn default() -> Self { + Self { + use_quoted_symbols: false, + indent_char: ' ', + indent_char_count: 3 + } + } +} + #[derive(Debug, Default, PartialEq)] pub struct DataString { data: Vec, @@ -39,6 +58,18 @@ impl From> for DataString { } } +impl From<&[u8]> for DataString { + fn from(data: &[u8]) -> DataString { + DataString::from_vec(data.to_vec()) + } +} + +impl From<&str> for DataString { + fn from(data: &str) -> DataString { + DataString::from_string(data) + } +} + #[derive(Debug, PartialEq)] pub enum DataArray { Integer(i32), @@ -155,6 +186,144 @@ impl DataArray { _ => None } } + + pub fn print(&self, stream: &mut T) -> Result<(), std::io::Error> { + self.print_with_format(stream, DTAFormat::default()) + } + + pub fn print_with_format(&self, stream: &mut T, format: DTAFormat) -> Result<(), std::io::Error> { + self.write_to_stream(stream, &format, 0, true).map(|_| ()) + } + + fn write_to_stream(&self, stream: &mut T, format: &DTAFormat, depth: u32, is_vertical: bool) -> Result<(bool, u32), std::io::Error> { + use std::io::Write; + + const CHAR_NEWLINE: u8 = b'\n'; + const CHAR_SPACE: u8 = b' '; + + // TODO: Move to separate function + fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32) -> Result<(bool, u32), std::io::Error> { + let _only_simple_types = elements.iter().all(|e| e.is_simple_type()); + + // TODO: Support vertical write + + // Always write first element without special spacing + for element in elements.iter().take(1) { + element.write_to_stream(stream, format, depth, false)?; + } + + for element in elements.iter().skip(1) { + stream.write_all(&[CHAR_SPACE])?; + element.write_to_stream(stream, format, depth, false)?; + } + + Ok((false, 0)) + } + + let is_newline = false; + + match self { + DataArray::Integer(i) => { + write!(stream, "{i}")?; + }, + DataArray::Float(f) => { + write!(stream, "{f:?}")?; // At least 1 decimal place... + }, + DataArray::Variable(v) => { + stream.write_all(b"$")?; + stream.write_all(&v.data)?; + }, + //DataArray::Func(_) => 0x03, + DataArray::Object(o) => { + todo!("Support object dta serialization") + }, + DataArray::Symbol(s) => { + if s.data.iter().any(|c| c.eq(&CHAR_SPACE)) { + // Write as string if it contains space + stream.write_all(b"\"")?; + stream.write_all(&s.data)?; + stream.write_all(b"\"")?; + } + else if format.use_quoted_symbols { + stream.write_all(b"\'")?; + stream.write_all(&s.data)?; + stream.write_all(b"\'")?; + } else { + stream.write_all(&s.data)?; + } + }, + DataArray::KDataUnhandled => { + stream.write_all(b"kDataUnhandled")?; + }, + DataArray::IfDef(t) => { + stream.write_all(b"#ifdef ")?; + stream.write_all(&t.data)?; + }, + DataArray::Else => { + stream.write_all(b"#else")?; + }, + DataArray::EndIf => { + stream.write_all(b"#endif")?; + }, + DataArray::Array(da) => { + stream.write_all(b"(")?; + write_elements(stream, da, format, depth)?; + stream.write_all(b")")?; + }, + DataArray::Command(da) => { + stream.write_all(b"{")?; + write_elements(stream, da, format, depth)?; + stream.write_all(b"}")?; + }, + DataArray::String(s) => { + // TODO: Escape certain characters... + stream.write_all(b"\"")?; + stream.write_all(&s.data)?; + stream.write_all(b"\"")?; + }, + DataArray::Property(da) => { + stream.write_all(b"[")?; + write_elements(stream, da, format, depth)?; + stream.write_all(b"]")?; + }, + DataArray::Define(t) => { + stream.write_all(b"#define ")?; + stream.write_all(&t.data)?; + }, + DataArray::Include(t) => { + stream.write_all(b"#include ")?; + stream.write_all(&t.data)?; + }, + DataArray::Merge(t) => { + stream.write_all(b"#merge ")?; + stream.write_all(&t.data)?; + }, + DataArray::IfNDef(t) => { + stream.write_all(b"#ifndef ")?; + stream.write_all(&t.data)?; + }, + DataArray::Autorun => { + stream.write_all(b"#autorun")?; + }, + DataArray::Undef(t) => { + stream.write_all(b"#undef ")?; + stream.write_all(&t.data)?; + }, + }; + + Ok((is_newline, depth)) + } + + fn is_simple_type(&self) -> bool { + match self { + DataArray::Integer(_) + | DataArray::Float(_) + | DataArray::Variable(_) + | DataArray::Symbol(_) + | DataArray::KDataUnhandled => true, + _ => false + } + } } #[derive(Debug, Default)] @@ -166,4 +335,48 @@ impl RootData { pub fn new() -> RootData { RootData::default() } +} + +#[cfg(test)] +mod tests { + use rstest::*; + use super::*; + + #[rstest] + #[case(DataArray::Integer(500), b"500")] + #[case(DataArray::Integer(-42), b"-42")] + #[case(DataArray::Float(0.0), b"0.0")] + #[case(DataArray::Float(0.3), b"0.3")] + #[case(DataArray::Float(0.38), b"0.38")] + #[case(DataArray::Float(-0.45), b"-0.45")] + #[case(DataArray::Variable("test".into()), b"$test")] + // TODO: Test case for func + #[case(DataArray::Symbol("test".into()), b"test")] + #[case(DataArray::Symbol("lol look at these spaces".into()), b"\"lol look at these spaces\"")] + #[case(DataArray::KDataUnhandled, b"kDataUnhandled")] + #[case(DataArray::IfDef("something1".into()), b"#ifdef something1")] + #[case(DataArray::Else, b"#else")] + #[case(DataArray::EndIf, b"#endif")] + #[case(DataArray::Array(vec![DataArray::Symbol(DataString::from_string("version")), DataArray::String(DataString::from_string("v123"))]), b"(version \"v123\")")] + // TODO: Test case for command + #[case(DataArray::String("test".into()), b"\"test\"")] + // TODO: Test case for property + #[case(DataArray::Define("whatever".into()), b"#define whatever")] + #[case(DataArray::Include("something.dta".into()), b"#include something.dta")] + #[case(DataArray::Merge("something.dta".into()), b"#merge something.dta")] + #[case(DataArray::IfNDef("whatever".into()), b"#ifndef whatever")] + #[case(DataArray::Autorun, b"#autorun")] + #[case(DataArray::Undef("whatever".into()), b"#undef whatever")] + fn print_data_array_test(#[case] data: DataArray, #[case] expected: &[u8; N]) { + use std::io::{BufWriter, Write}; + + let mut buffer = BufWriter::new(Vec::new()); + data.print(&mut buffer).unwrap(); + + let expected_str = std::str::from_utf8(expected).unwrap(); + let buffer_str = std::str::from_utf8(buffer.buffer()).unwrap(); + + //assert_eq!(expected, buffer.buffer()); + assert_eq!(expected_str, buffer_str); + } } \ No newline at end of file From d8ecb3c674ea928ef44d8607aa8a7d74018427cc Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 5 Jun 2023 22:37:22 -0400 Subject: [PATCH 060/113] Add todo for object test --- core/grim/src/dta/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index e1643334..58a9f49f 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -350,7 +350,7 @@ mod tests { #[case(DataArray::Float(0.38), b"0.38")] #[case(DataArray::Float(-0.45), b"-0.45")] #[case(DataArray::Variable("test".into()), b"$test")] - // TODO: Test case for func + // TODO: Test case for func + object #[case(DataArray::Symbol("test".into()), b"test")] #[case(DataArray::Symbol("lol look at these spaces".into()), b"\"lol look at these spaces\"")] #[case(DataArray::KDataUnhandled, b"kDataUnhandled")] From 6d4728867f25987f474976548feb344b4a5afdd8 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 6 Jun 2023 17:50:04 -0400 Subject: [PATCH 061/113] Support multiline data array printing --- core/grim/src/dta/mod.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index 58a9f49f..f48f4ed3 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -11,7 +11,7 @@ use parser::*; #[derive(Debug)] pub struct DTAFormat { pub use_quoted_symbols: bool, // Ex: 'shortsongname' - pub indent_char: char, + pub indent_char: u8, pub indent_char_count: u8, } @@ -19,7 +19,7 @@ impl Default for DTAFormat { fn default() -> Self { Self { use_quoted_symbols: false, - indent_char: ' ', + indent_char: b' ', indent_char_count: 3 } } @@ -203,18 +203,31 @@ impl DataArray { // TODO: Move to separate function fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32) -> Result<(bool, u32), std::io::Error> { - let _only_simple_types = elements.iter().all(|e| e.is_simple_type()); - - // TODO: Support vertical write + let only_simple_types = elements.iter().all(|e| e.is_simple_type()); // Always write first element without special spacing for element in elements.iter().take(1) { element.write_to_stream(stream, format, depth, false)?; } - for element in elements.iter().skip(1) { - stream.write_all(&[CHAR_SPACE])?; - element.write_to_stream(stream, format, depth, false)?; + if only_simple_types { + // Write as single line + for element in elements.iter().skip(1) { + stream.write_all(&[CHAR_SPACE])?; + element.write_to_stream(stream, format, depth + 1, false)?; + } + } else if !only_simple_types && elements.len() > 1 { + // Write on multiple lines + let indent_size = (format.indent_char_count as usize) * (depth as usize + 1); + let indent = (0..indent_size) + .map(|_| format.indent_char) + .collect::>(); + + for element in elements.iter().skip(1) { + stream.write_all(&[CHAR_NEWLINE])?; + stream.write_all(&indent)?; + element.write_to_stream(stream, format, depth + 1, false)?; + } } Ok((false, 0)) @@ -357,7 +370,9 @@ mod tests { #[case(DataArray::IfDef("something1".into()), b"#ifdef something1")] #[case(DataArray::Else, b"#else")] #[case(DataArray::EndIf, b"#endif")] - #[case(DataArray::Array(vec![DataArray::Symbol(DataString::from_string("version")), DataArray::String(DataString::from_string("v123"))]), b"(version \"v123\")")] + #[case(DataArray::Array(vec![DataArray::Symbol("version".into()), DataArray::Integer(123)]), b"(version 123)")] + #[case(DataArray::Array(vec![DataArray::Symbol("name".into()), DataArray::String("Doctor Worm".into())]), b"(name\n \"Doctor Worm\")")] + #[case(DataArray::Array(vec![DataArray::Symbol("doctorworm".into()), DataArray::Array(vec![DataArray::Symbol("name".into()), DataArray::String("Doctor Worm".into())])]), b"(doctorworm\n (name\n \"Doctor Worm\"))")] // TODO: Test case for command #[case(DataArray::String("test".into()), b"\"test\"")] // TODO: Test case for property From 16ee757e6702708a164d18fe02fe329628ada271 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 6 Jun 2023 20:48:44 -0400 Subject: [PATCH 062/113] Fix loading amp arks --- core/grim/src/ark/io.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/grim/src/ark/io.rs b/core/grim/src/ark/io.rs index f069962a..3ebc78ad 100644 --- a/core/grim/src/ark/io.rs +++ b/core/grim/src/ark/io.rs @@ -122,7 +122,7 @@ impl Ark { }; // Read entries - reader.seek(SeekFrom::Start(4)).unwrap(); + reader.seek(SeekFrom::Start(8)).unwrap(); // Skip version + entry count for id in 0..entry_count { let mut offset = 0; @@ -142,12 +142,18 @@ impl Ark { let size = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; let inflated_size = reader.read_uint32().map_err(|_| ArkReadError::ArkNotSupported)? as usize; - let file_name = &strings[file_name_idx]; - let dir_path = &strings[dir_path_idx]; - self.entries.push(ArkOffsetEntry { id, - path: create_full_path(dir_path, file_name), + path: if dir_path_idx < strings.len() { + // Join dir path + file name + let file_name = &strings[file_name_idx]; + let dir_path = &strings[dir_path_idx]; + + create_full_path(dir_path, file_name) + } else { + // Take just file name + strings[file_name_idx].to_owned() + }, offset: offset as u64, part: 0, size, From 474311fc5087d6840fd559dd8851ec175dbfacaa Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 6 Jun 2023 21:22:53 -0400 Subject: [PATCH 063/113] Print dta from root data --- core/grim/src/dta/mod.rs | 89 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index f48f4ed3..2124e710 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -2,12 +2,13 @@ mod errors; mod io; mod parser; -use std::todo; - pub use errors::*; pub use io::*; use parser::*; +const CHAR_NEWLINE: u8 = b'\n'; +const CHAR_SPACE: u8 = b' '; + #[derive(Debug)] pub struct DTAFormat { pub use_quoted_symbols: bool, // Ex: 'shortsongname' @@ -192,49 +193,10 @@ impl DataArray { } pub fn print_with_format(&self, stream: &mut T, format: DTAFormat) -> Result<(), std::io::Error> { - self.write_to_stream(stream, &format, 0, true).map(|_| ()) + self.write_to_stream(stream, &format, 0).map(|_| ()) } - fn write_to_stream(&self, stream: &mut T, format: &DTAFormat, depth: u32, is_vertical: bool) -> Result<(bool, u32), std::io::Error> { - use std::io::Write; - - const CHAR_NEWLINE: u8 = b'\n'; - const CHAR_SPACE: u8 = b' '; - - // TODO: Move to separate function - fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32) -> Result<(bool, u32), std::io::Error> { - let only_simple_types = elements.iter().all(|e| e.is_simple_type()); - - // Always write first element without special spacing - for element in elements.iter().take(1) { - element.write_to_stream(stream, format, depth, false)?; - } - - if only_simple_types { - // Write as single line - for element in elements.iter().skip(1) { - stream.write_all(&[CHAR_SPACE])?; - element.write_to_stream(stream, format, depth + 1, false)?; - } - } else if !only_simple_types && elements.len() > 1 { - // Write on multiple lines - let indent_size = (format.indent_char_count as usize) * (depth as usize + 1); - let indent = (0..indent_size) - .map(|_| format.indent_char) - .collect::>(); - - for element in elements.iter().skip(1) { - stream.write_all(&[CHAR_NEWLINE])?; - stream.write_all(&indent)?; - element.write_to_stream(stream, format, depth + 1, false)?; - } - } - - Ok((false, 0)) - } - - let is_newline = false; - + fn write_to_stream(&self, stream: &mut T, format: &DTAFormat, depth: u32) -> Result<(), std::io::Error> { match self { DataArray::Integer(i) => { write!(stream, "{i}")?; @@ -324,7 +286,7 @@ impl DataArray { }, }; - Ok((is_newline, depth)) + Ok(()) } fn is_simple_type(&self) -> bool { @@ -348,6 +310,45 @@ impl RootData { pub fn new() -> RootData { RootData::default() } + + pub fn print(&self, stream: &mut T) -> Result<(), std::io::Error> { + self.print_with_format(stream, DTAFormat::default()) + } + + pub fn print_with_format(&self, stream: &mut T, format: DTAFormat) -> Result<(), std::io::Error> { + write_elements(stream, &self.data, &format, 0) + } +} + +fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32) -> Result<(), std::io::Error> { + let only_simple_types = elements.iter().all(|e| e.is_simple_type()); + + // Always write first element without special spacing + for element in elements.iter().take(1) { + element.write_to_stream(stream, format, depth)?; + } + + if only_simple_types { + // Write as single line + for element in elements.iter().skip(1) { + stream.write_all(&[CHAR_SPACE])?; + element.write_to_stream(stream, format, depth + 1)?; + } + } else if !only_simple_types && elements.len() > 1 { + // Write on multiple lines + let indent_size = (format.indent_char_count as usize) * (depth as usize + 1); + let indent = (0..indent_size) + .map(|_| format.indent_char) + .collect::>(); + + for element in elements.iter().skip(1) { + stream.write_all(&[CHAR_NEWLINE])?; + stream.write_all(&indent)?; + element.write_to_stream(stream, format, depth + 1)?; + } + } + + Ok(()) } #[cfg(test)] From 0ff133cb6963ee7e4cd8b62c6058db41ab23ed46 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 6 Jun 2023 22:15:09 -0400 Subject: [PATCH 064/113] Fix dta print indention for root source --- core/grim/src/dta/mod.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index 2124e710..e3681f5f 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -242,12 +242,12 @@ impl DataArray { }, DataArray::Array(da) => { stream.write_all(b"(")?; - write_elements(stream, da, format, depth)?; + write_elements(stream, da, format, depth, false)?; stream.write_all(b")")?; }, DataArray::Command(da) => { stream.write_all(b"{")?; - write_elements(stream, da, format, depth)?; + write_elements(stream, da, format, depth, false)?; stream.write_all(b"}")?; }, DataArray::String(s) => { @@ -258,7 +258,7 @@ impl DataArray { }, DataArray::Property(da) => { stream.write_all(b"[")?; - write_elements(stream, da, format, depth)?; + write_elements(stream, da, format, depth, false)?; stream.write_all(b"]")?; }, DataArray::Define(t) => { @@ -316,11 +316,11 @@ impl RootData { } pub fn print_with_format(&self, stream: &mut T, format: DTAFormat) -> Result<(), std::io::Error> { - write_elements(stream, &self.data, &format, 0) + write_elements(stream, &self.data, &format, 0, true) } } -fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32) -> Result<(), std::io::Error> { +fn write_elements(stream: &mut T, elements: &Vec, format: &DTAFormat, depth: u32, is_root: bool) -> Result<(), std::io::Error> { let only_simple_types = elements.iter().all(|e| e.is_simple_type()); // Always write first element without special spacing @@ -336,7 +336,8 @@ fn write_elements(stream: &mut T, elements: &Vec, } } else if !only_simple_types && elements.len() > 1 { // Write on multiple lines - let indent_size = (format.indent_char_count as usize) * (depth as usize + 1); + let extra_indent = if is_root { 0 } else { 1 }; + let indent_size = (format.indent_char_count as usize) * (depth as usize + extra_indent); let indent = (0..indent_size) .map(|_| format.indent_char) .collect::>(); From d240fefa83bdc7b49b2b3cdfd1e88ea5fd654e3f Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 7 Jun 2023 19:44:36 -0400 Subject: [PATCH 065/113] Add acpcm decoder --- core/grim/src/audio/adpcm.rs | 91 ++++++++++++++++++++++++++++++++++++ core/grim/src/audio/mod.rs | 2 + 2 files changed, 93 insertions(+) create mode 100644 core/grim/src/audio/adpcm.rs diff --git a/core/grim/src/audio/adpcm.rs b/core/grim/src/audio/adpcm.rs new file mode 100644 index 00000000..4b7f520b --- /dev/null +++ b/core/grim/src/audio/adpcm.rs @@ -0,0 +1,91 @@ +// Reference: https://github.com/eurotools/es-ima-adpcm-encoder-decoder/tree/main + +const ADPCM_INDEX_TABLE: [i32; 16] = [ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +]; + +const ADPCM_STEP_SIZE_TABLE: [i32; 89] = [ + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767, +]; + +pub struct ADPCMDecoder { + state: (i32, i32) +} + +impl ADPCMDecoder { + pub fn new() -> Self { + Self { + state: (0, 0) + } + } + + pub fn reset(&mut self) { + self.state = (0, 0); + } + + pub fn decode(&mut self, data: &[u8]) -> Vec { + let mut buffer = vec![0i16; data.len() * 2]; + self.decode_with_buffer(data, &mut buffer); + buffer + } + + pub fn decode_with_buffer(&mut self, data: &[u8], buffer: &mut [i16]) { + let (ref mut prev_val, ref mut index) = self.state; + let mut step = ADPCM_STEP_SIZE_TABLE[*index as usize]; + + for i in 0..(data.len() * 2) { + // Get delta value + let delta = if (i & 1) == 0 { + data[i >> 1] & 0xf + } else { + (data[i >> 1] & 0xf0) >> 4 + }; + + // Get new index value (keep within step table range) + *index += ADPCM_INDEX_TABLE[delta as usize]; + if *index < 0 { + *index = 0; + } else if *index >= ADPCM_STEP_SIZE_TABLE.len() as i32 { + *index = (ADPCM_STEP_SIZE_TABLE.len() - 1) as i32; + } + + // Separate sign + magnitude + let sign = delta & 8; + let magnitude = delta & 7; + + // Compute difference + new predicted value + let mut vp_diff = step >> 3; + if (magnitude & 4) != 0 { vp_diff += step } + if (magnitude & 2) != 0 { vp_diff += step >> 1 } + if (magnitude & 1) != 0 { vp_diff += step >> 2 } + + *prev_val = if sign != 0 { + *prev_val - vp_diff + } else { + *prev_val + vp_diff + }; + + // Clamp output value + if *prev_val > std::i16::MAX as i32 { + *prev_val = std::i16::MAX as i32; + } else if *prev_val < std::i16::MIN as i32 { + *prev_val = std::i16::MIN as i32; + } + + // Update step value + step = ADPCM_STEP_SIZE_TABLE[*index as usize]; + + // Update sample value + buffer[i] = *prev_val as i16; + } + } +} \ No newline at end of file diff --git a/core/grim/src/audio/mod.rs b/core/grim/src/audio/mod.rs index 32c0361e..cf67da09 100644 --- a/core/grim/src/audio/mod.rs +++ b/core/grim/src/audio/mod.rs @@ -1,7 +1,9 @@ +mod adpcm; mod vgs; mod wav; mod xma; +pub use self::adpcm::*; pub use self::vgs::*; pub use self::wav::*; pub(crate) use self::xma::*; \ No newline at end of file From 6d5ad8dcac881283619d215fddf469d9826eebe7 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 7 Jun 2023 21:24:09 -0400 Subject: [PATCH 066/113] Add ability to decode amplitude str audio --- apps/cli/audio_tool/src/apps/decode.rs | 40 ++++++++++++++++++++++---- core/grim/src/audio/mod.rs | 22 +++++++++++++- core/grim/src/audio/str.rs | 23 +++++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 core/grim/src/audio/str.rs diff --git a/apps/cli/audio_tool/src/apps/decode.rs b/apps/cli/audio_tool/src/apps/decode.rs index 408acd52..c158facb 100644 --- a/apps/cli/audio_tool/src/apps/decode.rs +++ b/apps/cli/audio_tool/src/apps/decode.rs @@ -12,12 +12,13 @@ use std::path::{Path, PathBuf}; enum FileType { Vgs, + Str, SynthSample(u32, IOEndian) } #[derive(Parser, Debug)] pub struct DecoderApp { - #[arg(help = "Path to input audio (.vgs, SynthSample (Xbox 360))", required = true)] + #[arg(help = "Path to input audio (.vgs, .str, SynthSample (Xbox 360))", required = true)] pub input_path: String, #[arg(help = "Path to output audio (.wav, .xma (Only for Xbox 360 samples))", required = true)] pub output_path: String, @@ -29,6 +30,7 @@ impl SubApp for DecoderApp { let output_path = Path::new(&self.output_path); let input_type = match input_path.extension().and_then(|e| e.to_str()) { + Some(ext) if "str".eq_ignore_ascii_case(ext) => FileType::Str, Some(ext) if "vgs".eq_ignore_ascii_case(ext) => FileType::Vgs, Some(ext) => { // Guess file type from binary structure @@ -51,15 +53,17 @@ impl SubApp for DecoderApp { }; let input_type_name = match input_type { - FileType::Vgs => "VGS", + FileType::Str => "STR", FileType::SynthSample(_, _) => "SynthSample", + FileType::Vgs => "VGS", }; println!("Detected input file type of \"{input_type_name}\""); let _output_ext = match (&input_type, output_path.extension().and_then(|e| e.to_str())) { - (FileType::Vgs, Some(ext)) if "wav".eq_ignore_ascii_case(ext) => ext, + (FileType::Str, Some(ext)) if "wav".eq_ignore_ascii_case(ext) => ext, (FileType::SynthSample(_, _), Some(ext)) if "xma".eq_ignore_ascii_case(ext) => ext, + (FileType::Vgs, Some(ext)) if "wav".eq_ignore_ascii_case(ext) => ext, (_, Some(ext)) => { println!("Output audio with extension \".{ext}\" is not supported"); return Ok(()); @@ -71,13 +75,13 @@ impl SubApp for DecoderApp { }; match input_type { - FileType::Vgs => { + FileType::Str => { // Decode (returns interleaved audio samples) println!("Decoding..."); - let (sample_data, channels, sample_rate) = decode_vgs_file(input_path)?; + let sample_data = decode_str_file(input_path)?; // Encode to wav - let encoder = WavEncoder::new(&sample_data, channels, sample_rate); + let encoder = WavEncoder::new(&sample_data, 2, 48_000); encoder.encode_to_file(output_path).map_err(|e| Box::new(e) as Box)?; }, FileType::SynthSample(version, endian) => { @@ -92,6 +96,15 @@ impl SubApp for DecoderApp { let mut file = grim::io::create_new_file(output_path)?; file.write_all(&xma)?; }, + FileType::Vgs => { + // Decode (returns interleaved audio samples) + println!("Decoding..."); + let (sample_data, channels, sample_rate) = decode_vgs_file(input_path)?; + + // Encode to wav + let encoder = WavEncoder::new(&sample_data, channels, sample_rate); + encoder.encode_to_file(output_path).map_err(|e| Box::new(e) as Box)?; + }, }; println!("Wrote output to \"{}\"", output_path.to_str().unwrap_or_default()); @@ -120,6 +133,21 @@ fn decode_vgs_file(file_path: &Path) -> Result<(Vec, u16, u32), Box Result, std::io::Error> { + let mut input_file = std::fs::File::open(file_path)?; + + let mut input_data = Vec::new(); + input_file.read_to_end(&mut input_data)?; + + deinterleave_str(&mut input_data); + let samples = convert_to_samples(input_data); + + //let mut decoder = ADPCMDecoder::new(); + //let samples = decoder.decode(&input_data); + + Ok(samples) +} + fn generate_xma_from_synth_sample(file_path: &Path, info: &SystemInfo) -> Result, Box> { let mut stream = FileStream::from_path_as_read_open(file_path)?; diff --git a/core/grim/src/audio/mod.rs b/core/grim/src/audio/mod.rs index cf67da09..cf6b3e6f 100644 --- a/core/grim/src/audio/mod.rs +++ b/core/grim/src/audio/mod.rs @@ -1,9 +1,29 @@ mod adpcm; +mod str; mod vgs; mod wav; mod xma; pub use self::adpcm::*; +pub use self::str::*; pub use self::vgs::*; pub use self::wav::*; -pub(crate) use self::xma::*; \ No newline at end of file +pub(crate) use self::xma::*; + +pub fn convert_to_samples(data: Vec) -> Vec { + // Wait to stabilize https://github.com/rust-lang/rust/issues/74985 + /*data + .as_chunks() + .map(|d: [u8; 2]| i16::from_le_bytes(d)) + .collect::>()*/ + + let mut buffer = vec![0i16; data.len() / 2]; + + for (i, d) in data.chunks_exact(std::mem::size_of::()).enumerate() { + let v = i16::from_le_bytes([d[0], d[1]]); + + buffer[i] = v; + } + + buffer +} \ No newline at end of file diff --git a/core/grim/src/audio/str.rs b/core/grim/src/audio/str.rs new file mode 100644 index 00000000..01245c2d --- /dev/null +++ b/core/grim/src/audio/str.rs @@ -0,0 +1,23 @@ +const STR_INTERLEAVE_SIZE: usize = 512; +const STR_CHANNELS: usize = 2; + +pub fn deinterleave_str(data: &mut [u8]) { + let mut buffer = vec![0u8; STR_INTERLEAVE_SIZE * STR_CHANNELS]; + + for chunk in data.chunks_mut(STR_INTERLEAVE_SIZE * STR_CHANNELS) { + let half_size = chunk.len() / 2; + + // Left chunk + for (i, d) in chunk.iter().take(half_size).enumerate() { + buffer[((i >> 1) * 4) + (i & 1)] = *d; + } + + // Right chunk + for (i, d) in chunk.iter().skip(half_size).enumerate() { + buffer[((i >> 1) * 4) + (i & 1) + 2] = *d; + } + + // Copy buffer + chunk.copy_from_slice(&buffer[..chunk.len()]); + } +} \ No newline at end of file From d57fff151f4b83b264232bd7fc0d9c6d13825ca2 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 8 Jun 2023 21:29:14 -0400 Subject: [PATCH 067/113] Extend data array conversion options --- core/grim/src/dta/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/grim/src/dta/mod.rs b/core/grim/src/dta/mod.rs index e3681f5f..cce4cdcb 100644 --- a/core/grim/src/dta/mod.rs +++ b/core/grim/src/dta/mod.rs @@ -101,7 +101,23 @@ impl Default for DataArray { } } +impl From for DataArray { + fn from(value: i32) -> Self { + DataArray::Integer(value) + } +} + +impl From for DataArray { + fn from(value: f32) -> Self { + DataArray::Float(value) + } +} + impl DataArray { + pub fn from>(data: T) -> Self { + data.into() + } + pub(crate) fn get_enum_value(&self) -> u32 { match self { DataArray::Integer(_) => 0x00, From 64967771e56db95da3a13af30bbe46c28af6179e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 8 Jun 2023 22:03:55 -0400 Subject: [PATCH 068/113] Update gltf --- core/grim/Cargo.toml | 4 ++-- core/grim/src/model/export.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/grim/Cargo.toml b/core/grim/Cargo.toml index a186934d..2ae4e27a 100644 --- a/core/grim/Cargo.toml +++ b/core/grim/Cargo.toml @@ -10,8 +10,8 @@ bitstream-io = { version = "1.6.0", optional = true } ffmpeg-next = { version = "6.0.0", optional = true } flate2 = "1.0.25" fon = { version = "0.6.0", optional = true } -gltf = { version = "1.1.0", optional = true, features = [ "import", "names" ] } -gltf-json = { version = "1.1.0", optional = true, features = [ "names" ] } +gltf = { version = "1.2.0", optional = true, features = [ "import", "names" ] } +gltf-json = { version = "1.2.0", optional = true, features = [ "names" ] } grim_macros = { path = "../grim_macros" } grim_traits = { path = "../grim_traits" } half = { version = "2.2.1", default-features = false } diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index df611055..7a64b2b9 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -7,7 +7,7 @@ use itertools::*; use gltf_json as json; use nalgebra as na; use serde::ser::Serialize; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::error::Error; use std::io::Write; use std::path::{Path, PathBuf}; @@ -1235,7 +1235,7 @@ impl GltfExporter { primitives: vec![ json::mesh::Primitive { attributes: { - let mut map = HashMap::new(); + let mut map = BTreeMap::new(); // Add positions if let Some(acc_idx) = pos_idx { From 5e9c12de3fbf46a4fff41299c5776009a9502332 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 8 Jun 2023 22:30:59 -0400 Subject: [PATCH 069/113] Support gzip inflation for amp milos --- core/grim/src/io/archive.rs | 2 +- core/grim/src/io/compression.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core/grim/src/io/archive.rs b/core/grim/src/io/archive.rs index 18bb8a3e..60bbe46d 100644 --- a/core/grim/src/io/archive.rs +++ b/core/grim/src/io/archive.rs @@ -102,7 +102,7 @@ impl MiloArchive { (BlockType::TypeA, _) | (BlockType::TypeD, false) => bytes, // No compression (BlockType::TypeB, _) => inflate_zlib_block(&bytes, &mut buffer[..])?, - (BlockType::TypeC, _) => todo!("Gzip compression not supported!"), // TODO: Support gzip + (BlockType::TypeC, _) => inflate_gzip_block(&bytes, &mut buffer[..])?, (BlockType::TypeD, true) => inflate_zlib_block(&bytes[4..], &mut buffer[..])?, // Skip 4-byte inflated size prefix }; diff --git a/core/grim/src/io/compression.rs b/core/grim/src/io/compression.rs index 7d876182..dab3c44f 100644 --- a/core/grim/src/io/compression.rs +++ b/core/grim/src/io/compression.rs @@ -1,7 +1,9 @@ //use flate2::{Compress, Decompress}; use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status}; +use flate2::read::GzDecoder; use std::error::Error; +use std::io::Read; pub fn inflate_zlib_block(data: &[u8], buffer: &mut [u8]) -> Result, Box> { @@ -28,6 +30,21 @@ pub fn inflate_zlib_block(data: &[u8], buffer: &mut [u8]) -> Result, Box } } +pub fn inflate_gzip_block(data: &[u8], buffer: &mut [u8]) -> Result, Box> { + if data.is_empty() { + // Fast exit + return Ok(Vec::new()); + } + + let mut decoder = GzDecoder::new(data); + let inflate_size = decoder.read(buffer)?; + + let mut inflated_data = vec![0u8; inflate_size]; + inflated_data.clone_from_slice(&buffer[..inflate_size]); + + Ok(inflated_data) +} + pub fn deflate_zlib_block(data: &[u8], buffer: &mut [u8]) -> Result, Box> { let mut compressor = Compress::new(Compression::best(), false); let status = compressor.compress(data, buffer, FlushCompress::Finish)?; From ffc0c367d65e6a6c0d40812141077b5f87575746 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 11 Jun 2023 11:34:36 -0400 Subject: [PATCH 070/113] Create load errors for mesh + group --- core/grim/src/scene/group/io.rs | 14 ++++++++++++-- core/grim/src/scene/mesh/io.rs | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/core/grim/src/scene/group/io.rs b/core/grim/src/scene/group/io.rs index 8f6d36f3..34dd804b 100644 --- a/core/grim/src/scene/group/io.rs +++ b/core/grim/src/scene/group/io.rs @@ -4,6 +4,15 @@ use crate::SystemInfo; use grim_traits::scene::*; use std::collections::HashSet; use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum GroupLoadError { + #[error("Group version {version} is not supported")] + GroupVersionNotSupported { + version: u32 + }, +} fn is_version_supported(version: u32) -> bool { match version { @@ -22,8 +31,9 @@ impl ObjectReadWrite for GroupObject { let version = reader.read_uint32()?; if !is_version_supported(version) { - // TODO: Switch to custom error - panic!("Group version \"{}\" is not supported!", version); + return Err(Box::new(GroupLoadError::GroupVersionNotSupported { + version + })); } load_object(self, &mut reader, info)?; diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index 844dde5f..f76cf9f7 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -4,6 +4,15 @@ use crate::scene::*; use crate::SystemInfo; use grim_traits::scene::*; use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum MeshLoadError { + #[error("Mesh version {version} is not supported")] + MeshVersionNotSupported { + version: u32 + }, +} fn is_version_supported(version: u32) -> bool { match version { @@ -22,8 +31,9 @@ impl ObjectReadWrite for MeshObject { let version = reader.read_uint32()?; if !is_version_supported(version) { - // TODO: Switch to custom error - panic!("Mesh version \"{}\" is not supported!", version); + return Err(Box::new(MeshLoadError::MeshVersionNotSupported { + version + })); } load_object(self, &mut reader, info)?; From 238eabb320f68034ccd31f5c4ae93c78c013fb7a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 11 Jun 2023 11:52:27 -0400 Subject: [PATCH 071/113] Replace print statements with log --- apps/ui/preview_ui/Cargo.toml | 1 + apps/ui/preview_ui/src/main.rs | 29 +++++++++++---------- apps/ui/preview_ui/src/plugins.rs | 3 ++- apps/ui/preview_ui/src/render/milo_entry.rs | 5 ++-- apps/ui/preview_ui/src/render/mod.rs | 14 +++++----- apps/ui/preview_ui/src/settings.rs | 3 ++- apps/ui/preview_ui/src/state.rs | 3 ++- core/grim/src/scene/object_dir.rs | 3 +++ 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/apps/ui/preview_ui/Cargo.toml b/apps/ui/preview_ui/Cargo.toml index 0d0b9741..9a0cac94 100644 --- a/apps/ui/preview_ui/Cargo.toml +++ b/apps/ui/preview_ui/Cargo.toml @@ -14,6 +14,7 @@ font-awesome-as-a-crate = "0.3.0" grim = { workspace = true } itertools = { workspace = true } lazy_static = { workspace = true } +log = { workspace = true } # native-dialog = "0.6.1" serde = { workspace = true } serde_json = { workspace = true } diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs index f929d1e0..c172cb46 100644 --- a/apps/ui/preview_ui/src/main.rs +++ b/apps/ui/preview_ui/src/main.rs @@ -22,6 +22,7 @@ use bevy_infinite_grid::{GridShadowCamera, InfiniteGridBundle, InfiniteGrid, Inf use grim::*; use grim::ark::{Ark, ArkOffsetEntry}; use grim::scene::*; +use log::{debug, info, warn}; use plugins::*; use state::*; use std::{env::args, path::{Path, PathBuf}}; @@ -244,7 +245,7 @@ fn consume_app_events( commands.entity(entity).despawn_recursive(); } if i > 0 { - println!("Removed {} meshes in scene", i); + debug!("Removed {} meshes in scene", i); } /*if render_entry { @@ -278,7 +279,7 @@ fn consume_app_events( state.milo_view.selected_entry = entry_name.to_owned(); - println!("Updated milo"); + debug!("Updated milo"); }, AppEvent::ToggleGridLines(show) => { *grid.single_mut() = if *show { @@ -306,7 +307,7 @@ fn consume_app_events( ); } - println!("Updated milo"); + debug!("Updated milo"); },*/ } } @@ -321,11 +322,11 @@ fn open_file( if ext.contains("hdr") { // Open ark - println!("Opening hdr from \"{}\"", file_path.display()); + info!("Opening hdr from \"{}\"", file_path.display()); let ark_res = Ark::from_path(file_path); if let Ok(ark) = ark_res { - println!("Successfully opened ark with {} entries", ark.entries.len()); + debug!("Successfully opened ark with {} entries", ark.entries.len()); state.root = Some(create_ark_tree(&ark)); state.ark = Some(ark); @@ -334,11 +335,11 @@ fn open_file( || ext.contains("gh") || ext.contains("rnd") { // TODO: Break out into static regex // Open milo - println!("Opening milo from \"{}\"", file_path.display()); + info!("Opening milo from \"{}\"", file_path.display()); match open_and_unpack_milo(file_path) { Ok((milo, info)) => { - println!("Successfully opened milo with {} entries", milo.get_entries().len()); + debug!("Successfully opened milo with {} entries", milo.get_entries().len()); state.milo = Some(milo); state.system_info = Some(info); @@ -369,12 +370,12 @@ fn open_file( app_event_writer.send(AppEvent::SelectMiloEntry(selected_entry)); }, - Err(_err) => { - // TODO: Log error + Err(err) => { + warn!("Unable to unpack milo file:\n\t: {:?}", err); } } } else { - println!("Unknown file type \"{}\"", file_path.display()); + info!("Unknown file type \"{}\"", file_path.display()); } } @@ -449,9 +450,9 @@ fn window_resized( if settings.maximized != maximized { if maximized { - println!("Window maximized"); + debug!("Window maximized"); } else { - println!("Window unmaximized"); + debug!("Window unmaximized"); } settings.maximized = maximized; @@ -465,7 +466,7 @@ fn window_resized( } for e in resize_events.iter() { - println!("Window resized: {}x{}", e.width as u32, e.height as u32); + debug!("Window resized: {}x{}", e.width as u32, e.height as u32); settings.window_width = e.width; settings.window_height = e.height; @@ -479,7 +480,7 @@ fn drop_files( ) { for d in drag_drop_events.iter() { if let FileDragAndDrop::DroppedFile { path_buf, .. } = d { - println!("Dropped \"{}\"", path_buf.to_str().unwrap()); + debug!("Dropped \"{}\"", path_buf.to_str().unwrap()); file_event_writer.send(AppFileEvent::Open(path_buf.to_owned())); } diff --git a/apps/ui/preview_ui/src/plugins.rs b/apps/ui/preview_ui/src/plugins.rs index c681a0a2..cc6844e5 100644 --- a/apps/ui/preview_ui/src/plugins.rs +++ b/apps/ui/preview_ui/src/plugins.rs @@ -1,6 +1,7 @@ use bevy::{prelude::*, log::LogPlugin, app::PluginGroupBuilder, window::{PresentMode, WindowMode, WindowResized, WindowResolution}}; use crate::settings::*; use crate::state::*; +use log::info; use std::{env::args, path::{Path, PathBuf}}; const SETTINGS_FILE_NAME: &str = "settings.json"; @@ -89,7 +90,7 @@ fn load_state() -> AppState { fn load_settings(settings_path: &Path) -> AppSettings { let settings = AppSettings::load_from_file(settings_path); - println!("Loaded settings from \"{}\"", settings_path.to_str().unwrap()); + info!("Loaded settings from \"{}\"", settings_path.to_str().unwrap()); settings } \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs index 82da2cb1..69984d1f 100644 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ b/apps/ui/preview_ui/src/render/milo_entry.rs @@ -3,6 +3,7 @@ use bevy::render::render_resource::{AddressMode, Extent3d, SamplerDescriptor, Te use bevy::render::texture::ImageSampler; use itertools::*; +use log::warn; use std::collections::HashMap; use std::error::Error; @@ -305,7 +306,7 @@ fn get_computed_mat<'a>( } if !parent_name.is_empty() { - println!("Can't find trans for {}", parent_name); + warn!("Can't find trans for {}", parent_name); } map_matrix(milo_object.get_world_xfm()) @@ -330,7 +331,7 @@ fn get_product_local_mat<'a>( } if parent_name.is_empty() { - println!("Can't find trans for {}", parent_name); + warn!("Can't find trans for {}", parent_name); } map_matrix(milo_object.get_local_xfm()) diff --git a/apps/ui/preview_ui/src/render/mod.rs b/apps/ui/preview_ui/src/render/mod.rs index b752904b..277bbd1e 100644 --- a/apps/ui/preview_ui/src/render/mod.rs +++ b/apps/ui/preview_ui/src/render/mod.rs @@ -7,6 +7,8 @@ use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; pub use loader::*; pub use milo_entry::*; +use log::{debug, info, warn, error}; + use std::collections::HashMap; use std::error::Error; use std::fs; @@ -102,7 +104,7 @@ pub fn render_milo( // ATI2 = Bc5RgUnorm match bitmap.unpack_rgba(system_info) { Ok(rgba) => { - println!("Processing {}", tex.get_name()); + debug!("Processing {}", tex.get_name()); // TODO: Figure out how bevy can support mip maps let tex_size = (bitmap.width as usize) * (bitmap.height as usize) * 4; @@ -121,13 +123,13 @@ pub fn render_milo( tex_map.insert(tex.get_name().as_str(), bevy_tex); }, Err(_err) => { - println!("Failed to convert {}", tex.get_name()); + error!("Failed to convert {}", tex.get_name()); } } } } - println!("Found {} groups, {} meshes, {} textures, and {} materials", groups.len(), meshes.len(), textures.len(), mats.len()); + debug!("Found {} groups, {} meshes, {} textures, and {} materials", groups.len(), meshes.len(), textures.len(), mats.len()); for mesh in meshes { // Ignore meshes without geometry (used mostly in GH1) @@ -183,11 +185,11 @@ pub fn render_milo( .find(|m| m.get_name().eq(&mesh.mat)); if mat.is_none() { - println!("Mat not found for \"{}\"", &mesh.mat); + warn!("Mat not found for \"{}\"", &mesh.mat); } else { let mat = mat.unwrap(); if !mat.diffuse_tex.is_empty() && tex_map.get(mat.diffuse_tex.as_str()).is_none() { - println!("Diffuse tex not found for \"{}\"", &mat.diffuse_tex); + warn!("Diffuse tex not found for \"{}\"", &mat.diffuse_tex); } } @@ -237,7 +239,7 @@ pub fn render_milo( ..Default::default() }); - println!("Added {}", &mesh.name); + debug!("Added {}", &mesh.name); } } diff --git a/apps/ui/preview_ui/src/settings.rs b/apps/ui/preview_ui/src/settings.rs index 24f7bb86..30fe11de 100644 --- a/apps/ui/preview_ui/src/settings.rs +++ b/apps/ui/preview_ui/src/settings.rs @@ -1,5 +1,6 @@ use bevy::prelude::*; use serde::{Deserialize, Serialize}; +use log::error; use std::fmt::{self, Display, Formatter}; use std::fs::{read_to_string, self}; use std::path::Path; @@ -76,7 +77,7 @@ impl AppSettings { let settings = serde_json::from_str::(&text); if let Err(err) = &settings { - println!("Unable to parse settings: {:?}", err); + error!("Unable to parse settings: {:?}", err); } return settings.unwrap_or_default(); diff --git a/apps/ui/preview_ui/src/state.rs b/apps/ui/preview_ui/src/state.rs index ac8c7d3d..2a84bcf5 100644 --- a/apps/ui/preview_ui/src/state.rs +++ b/apps/ui/preview_ui/src/state.rs @@ -4,6 +4,7 @@ use grim::*; use grim::ark::{Ark, ArkOffsetEntry}; use grim::scene::*; use itertools::Itertools; +use log::debug; use std::{env::args, path::{Path, PathBuf}}; type ConsumeEventFn = fn(AppEvent); @@ -33,7 +34,7 @@ pub struct AppState { impl AppState { pub fn save_settings(&self, settings: &AppSettings) { settings.save_to_file(&self.settings_path); - println!("Saved settings to \"{}\"", &self.settings_path.to_str().unwrap()); + debug!("Saved settings to \"{}\"", &self.settings_path.to_str().unwrap()); } pub fn consume_events(&mut self, mut callback: impl FnMut(AppEvent)) { diff --git a/core/grim/src/scene/object_dir.rs b/core/grim/src/scene/object_dir.rs index 26fc0727..806ea83e 100644 --- a/core/grim/src/scene/object_dir.rs +++ b/core/grim/src/scene/object_dir.rs @@ -2,6 +2,7 @@ use crate::{SystemInfo}; use crate::io::{BinaryStream, FileSearchDepth, FileStream, MemoryStream, PathFinder, SeekFrom, Stream}; use crate::scene::*; use lazy_static::lazy_static; +use log::warn; use regex::Regex; use std::error::Error; @@ -102,6 +103,8 @@ impl<'a> ObjectDir { for entry in obj_dir.entries.iter_mut() { if let Some(new_entry) = entry.unpack(info) { *entry = new_entry; + } else { + warn!("Unable to unpack \"{}\" ({})", entry.get_name(), entry.get_type()); } } } From af6b8304fc00725a41cdd4e4e7278f289494d1ef Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 11 Jun 2023 18:30:17 -0400 Subject: [PATCH 072/113] Support loading amp mesh --- core/grim/src/scene/draw/io.rs | 23 ++++- core/grim/src/scene/mesh/io.rs | 157 ++++++++++++++++++++++++++++++-- core/grim/src/scene/trans/io.rs | 72 +++++++++++++-- 3 files changed, 233 insertions(+), 19 deletions(-) diff --git a/core/grim/src/scene/draw/io.rs b/core/grim/src/scene/draw/io.rs index 0cf89c04..2a6c240a 100644 --- a/core/grim/src/scene/draw/io.rs +++ b/core/grim/src/scene/draw/io.rs @@ -3,9 +3,19 @@ use crate::scene::*; use crate::SystemInfo; use grim_traits::scene::*; use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum DrawLoadError { + #[error("Draw version {version} is not supported")] + DrawVersionNotSupported { + version: u32 + }, +} fn is_version_supported(version: u32) -> bool { match version { + 0 => true, // Amp Demo/Amp 1 => true, // GH1 3 | 4 => true, // TBRB/GDRB _ => false @@ -29,8 +39,9 @@ impl ObjectReadWrite for DrawObject { pub(crate) fn load_draw(draw: &mut T, reader: &mut Box, info: &SystemInfo, read_meta: bool) -> Result<(), Box> { let version = reader.read_uint32()?; if !is_version_supported(version) { - // TODO: Switch to custom error - panic!("Draw version \"{}\" is not supported!", version); + return Err(Box::new(DrawLoadError::DrawVersionNotSupported { + version + })); } if read_meta { @@ -39,7 +50,7 @@ pub(crate) fn load_draw(draw: &mut T, reader: &mut Box, i draw.set_showing(reader.read_boolean()?); - if version < 3 { + if version < 2 { let draw_objects = draw.get_draw_objects_mut(); draw_objects.clear(); @@ -50,9 +61,11 @@ pub(crate) fn load_draw(draw: &mut T, reader: &mut Box, i } } - load_sphere(draw.get_sphere_mut(), reader)?; + if version > 0 { + load_sphere(draw.get_sphere_mut(), reader)?; + } - if version >= 3 { + if version > 2 { draw.set_draw_order(reader.read_float32()?); } diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index f76cf9f7..64d18d73 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -16,6 +16,7 @@ pub enum MeshLoadError { fn is_version_supported(version: u32) -> bool { match version { + 13 | 14 => true, // Amp Demo/Amp 25 => true, // GH1 28 => true, // GH2/GH2 360 34 => true, // RB1/RB2 @@ -40,15 +41,110 @@ impl ObjectReadWrite for MeshObject { load_trans(self, &mut reader, info, false)?; load_draw(self, &mut reader, info, false)?; + if version < 15 { + _ = reader.read_prefixed_string()?; // Some string + + // Read bones + self.bones.clear(); + let bone_count = reader.read_uint32()?; + + for _ in 0..bone_count { + let mut bone = BoneTrans::default(); + + bone.name = reader.read_prefixed_string()?; + self.bones.push(bone); + } + } + + if version < 20 { + // Skip 2 ints + reader.seek(SeekFrom::Current(8))?; + } + + if version < 3 { + // Skip int + reader.seek(SeekFrom::Current(4))?; + } + self.mat = reader.read_prefixed_string()?; + if version == 27 { + // Secondary material? + _ = reader.read_prefixed_string()?; + } + self.geom_owner = reader.read_prefixed_string()?; + if version < 13 { + // Secondary geom owner? + _ = reader.read_prefixed_string()?; + } + + if version < 15 { + // Set on mesh instead of base trans object + let trans_parent = reader.read_prefixed_string()?; + self.set_parent(trans_parent); + } + + if version < 14 { + // Skip empty RndTransformable strings + _ = reader.read_prefixed_string()?; + _ = reader.read_prefixed_string()?; + } + + if version < 3 { + // Skip vector3 + reader.seek(SeekFrom::Current(12))?; + } + + if version < 15 { + // Set on mesh instead of base draw object + load_sphere(self.get_sphere_mut(), &mut reader)?; + } + + if version < 8 { + // Skip some bool + _ = reader.read_boolean()?; + } + + if version < 15 { + // Skip unknown string + float + _ = reader.read_prefixed_string()?; + reader.seek(SeekFrom::Current(4))?; + } + + // Read mutable value + if version < 16 { + if version > 11 { + _ = reader.read_boolean()?; + } + + // Just hard-code as default value + self.mutable = Mutable::kMutableNone; + } else { + self.mutable = reader.read_uint32()?.into(); + } + + // Read volume value + if version > 17 { + self.volume = reader.read_uint32()?.into(); + } + + // Read bsp value + if version > 18 { + // Ignored but still validated + let bsp = reader.read_uint8()?; + if bsp != 0 { + panic!("Expected bsp field to be 0, not \"{}\" in Mesh", bsp); + } + } - self.mutable = reader.read_uint32()?.into(); - self.volume = reader.read_uint32()?.into(); + if version == 7 { + // Skip another unknown bool + _ = reader.read_boolean()?; + } - let bsp = reader.read_uint8()?; - if bsp != 0 { - panic!("Expected bsp field to be 0, not \"{}\" in Mesh", bsp); + if version < 11 { + // Skip unknown int + reader.seek(SeekFrom::Current(4))?; } let vert_count = reader.read_uint32()?; @@ -66,6 +162,41 @@ impl ObjectReadWrite for MeshObject { self.vertices.clear(); self.raw_vertices.clear(); for _ in 0..vert_count { + let mut vec = Vert::default(); + + // TODO: Should probably clean up this loop + if version <= 14 { + // Amp (56 bytes) + // Position + vec.pos.x = reader.read_float32()?; + vec.pos.y = reader.read_float32()?; + vec.pos.z = reader.read_float32()?; + + // Bone indices + vec.bones[0] = reader.read_uint16()?; + vec.bones[1] = reader.read_uint16()?; + vec.bones[2] = reader.read_uint16()?; + vec.bones[3] = reader.read_uint16()?; + + // Normals + vec.normals.x = reader.read_float32()?; + vec.normals.y = reader.read_float32()?; + vec.normals.z = reader.read_float32()?; + + // Weights + vec.weights[0] = reader.read_float32()?; + vec.weights[1] = reader.read_float32()?; + vec.weights[2] = reader.read_float32()?; + vec.weights[3] = reader.read_float32()?; + + // UVs + vec.uv.u = reader.read_float32()?; + vec.uv.v = reader.read_float32()?; + + self.vertices.push(vec); + continue; + } + // TODO: Remove once next gen vertex format is figured out if version >= 36 && is_ng { // Read raw vert data @@ -77,8 +208,6 @@ impl ObjectReadWrite for MeshObject { self.raw_vertices.push(raw_vert); } - let mut vec = Vert::default(); - // Position vec.pos.x = reader.read_float32()?; vec.pos.y = reader.read_float32()?; @@ -211,6 +340,20 @@ impl ObjectReadWrite for MeshObject { self.faces.push(face); } + if version < 24 { + // Read pairs of shorts, matches face count + // Not always present, skip for now + let short_count = reader.read_uint32()?; + reader.seek(SeekFrom::Current((short_count * 2) as i64 * std::mem::size_of::() as i64))?; + + if version >= 14 { + // Skip int + reader.seek(SeekFrom::Current(4))?; + } + + return Ok(()); + } + let group_count = reader.read_uint32()?; self.face_groups = reader.read_bytes(group_count as usize)?; diff --git a/core/grim/src/scene/trans/io.rs b/core/grim/src/scene/trans/io.rs index cc6e4cfe..9a191e70 100644 --- a/core/grim/src/scene/trans/io.rs +++ b/core/grim/src/scene/trans/io.rs @@ -3,9 +3,19 @@ use crate::scene::*; use crate::SystemInfo; use grim_traits::scene::*; use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum TransLoadError { + #[error("Trans version {version} is not supported")] + TransVersionNotSupported { + version: u32 + }, +} fn is_version_supported(version: u32) -> bool { match version { + 5 => true, // Amp/Amp Demo 8 => true, // GH1 9 => true, // GH2/RB1/RB2/TBRB/GDRB _ => false @@ -29,8 +39,9 @@ impl ObjectReadWrite for TransObject { pub(crate) fn load_trans(trans: &mut T, reader: &mut Box, info: &SystemInfo, read_meta: bool) -> Result<(), Box> { let version = reader.read_uint32()?; if !is_version_supported(version) { - // TODO: Switch to custom error - panic!("Trans version \"{}\" is not supported!", version); + return Err(Box::new(TransLoadError::TransVersionNotSupported { + version + })); } if read_meta { @@ -40,7 +51,7 @@ pub(crate) fn load_trans(trans: &mut T, reader: &mut Box load_matrix(trans.get_local_xfm_mut(), reader)?; load_matrix(trans.get_world_xfm_mut(), reader)?; - if version <= 8 { + if version < 9 { let trans_objects = trans.get_trans_objects_mut(); trans_objects.clear(); @@ -51,11 +62,58 @@ pub(crate) fn load_trans(trans: &mut T, reader: &mut Box } } - trans.set_constraint(reader.read_uint32()?.into()); - trans.set_target(reader.read_prefixed_string()?); + // Read constraint + if version > 6 { + trans.set_constraint(reader.read_uint32()?.into()); + } else if version == 6 { + _ = reader.read_uint32()?; + trans.set_constraint(TransConstraint::kConstraintNone); + } else if version < 3 { + if version > 0 { + _ = reader.read_uint32()?; + } + + trans.set_constraint(TransConstraint::kConstraintNone); + } else { // 3, 4, 5 + _ = reader.read_uint32()?; // Some flags + } + + if version < 7 { + // Skip 3 ints + reader.seek(SeekFrom::Current(12))?; + } + + if version < 5 { + // Skip bool + reader.seek(SeekFrom::Current(1))?; + } + + if version < 2 { + // Skip vector4 + reader.seek(SeekFrom::Current(16))?; + } + + if version > 5 { + trans.set_target(reader.read_prefixed_string()?); + } else { + trans.set_target(String::new()); + } + + if version > 6 { + trans.set_preserve_scale(reader.read_boolean()?); + } else { + trans.set_preserve_scale(true); + } - trans.set_preserve_scale(reader.read_boolean()?); - trans.set_parent(reader.read_prefixed_string()?); + if version < 9 { + if version >= 7 { + trans.set_parent(reader.read_prefixed_string()?); + } else { + trans.set_parent(String::new()); + } + } else { + trans.set_parent(reader.read_prefixed_string()?); + } Ok(()) } From ac027e6c5231ba994da7c525d55a5ca20f2c1e3b Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 11 Jun 2023 18:36:21 -0400 Subject: [PATCH 073/113] Add custom cam load error --- core/grim/src/scene/cam/io.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/grim/src/scene/cam/io.rs b/core/grim/src/scene/cam/io.rs index cd0fec3e..571487eb 100644 --- a/core/grim/src/scene/cam/io.rs +++ b/core/grim/src/scene/cam/io.rs @@ -3,6 +3,15 @@ use crate::scene::*; use crate::SystemInfo; use grim_traits::scene::*; use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum CamLoadError { + #[error("Cam version {version} is not supported")] + CamVersionNotSupported { + version: u32 + }, +} fn is_version_supported(version: u32) -> bool { match version { @@ -18,8 +27,9 @@ impl ObjectReadWrite for CamObject { let version = reader.read_uint32()?; if !is_version_supported(version) { - // TODO: Switch to custom error - panic!("Cam version \"{}\" is not supported!", version); + return Err(Box::new(CamLoadError::CamVersionNotSupported { + version + })); } load_object(self, &mut reader, info)?; From 182341b8d4d5643e828d02e33625353d9e61431c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 18:40:02 -0400 Subject: [PATCH 074/113] Support loading amp texture --- core/grim/src/scene/tex/io.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/grim/src/scene/tex/io.rs b/core/grim/src/scene/tex/io.rs index efe4f5d2..be61c106 100644 --- a/core/grim/src/scene/tex/io.rs +++ b/core/grim/src/scene/tex/io.rs @@ -18,8 +18,9 @@ impl Tex { fn is_magic_valid(magic: u32, info: &SystemInfo) -> bool { match info.version { - // GH1 + // Amp/GH1 10 => match magic { + 5 => true, // Amp 8 => true, _ => false }, @@ -75,7 +76,13 @@ impl ObjectReadWrite for Tex { self.bpp = reader.read_uint32()?; self.ext_path = reader.read_prefixed_string()?; - self.index_f = reader.read_float32()?; + + if magic >= 8 { + self.index_f = reader.read_float32()? + } else { + self.index_f = 0.0 + } + self.index = reader.read_int32()?; // RB3 encoding From c358c6e1eb8c071aaf7e900f7c3403a633f51dac Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 19:19:57 -0400 Subject: [PATCH 075/113] Support loading amp mats --- core/grim/src/scene/mat/io.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/core/grim/src/scene/mat/io.rs b/core/grim/src/scene/mat/io.rs index 19665f47..fc7cd3f9 100644 --- a/core/grim/src/scene/mat/io.rs +++ b/core/grim/src/scene/mat/io.rs @@ -15,11 +15,12 @@ pub enum MatLoadError { fn is_version_supported(version: u32) -> bool { match version { - 21 => true, // GH1 + 8 | 9 => true, // Amp Demo/Amp + 21 => true, // GH1 25 | 27 | 28 => true, // GH2 4-song/GH2/GH2 360 - 41 | 47 => true, // RB1/RB2 - 55 | 56 => true, // TBRB/GDRB - 68 => true, // RB3 + 41 | 47 => true, // RB1/RB2 + 55 | 56 => true, // TBRB/GDRB + 68 => true, // RB3 _ => false } } @@ -37,7 +38,30 @@ impl ObjectReadWrite for MatObject { load_object(self, &mut reader, info)?; - if version <= 21 { + // Amp/GH1 mats can be linked to many textures + if version <= 9 { + // Read tex entries + let tex_count = reader.read_uint32()?; + + for _ in 0..tex_count { + let map_type = reader.read_uint32()?; + + // Skip unknown data + reader.seek(SeekFrom::Current(61))?; + + // Set name + let name = reader.read_prefixed_string()?; + match map_type { + 2 => self.emissive_map = name, + 4 => self.diffuse_tex = name, + 5 => self.environ_map = name, + _ => continue, + }; + } + + // Skip remaining unknown crap + return Ok(()); + } else if version <= 21 { // Read tex entries let tex_count = reader.read_uint32()?; From 9de00e8640394bd3cff64daf6b61ad2dbbed7cc6 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 19:48:35 -0400 Subject: [PATCH 076/113] Support loading amp bitmap --- core/grim/src/texture/io.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/core/grim/src/texture/io.rs b/core/grim/src/texture/io.rs index 168dbd70..a3c3328d 100644 --- a/core/grim/src/texture/io.rs +++ b/core/grim/src/texture/io.rs @@ -231,17 +231,32 @@ impl ObjectReadWrite for Bitmap { fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); - let _byte_1 = reader.read_uint8()?; // TODO: Verify always 1 - - self.bpp = reader.read_uint8()?; - self.encoding = reader.read_uint32()?; - self.mip_maps = reader.read_uint8()?; - - self.width = reader.read_uint16()?; - self.height = reader.read_uint16()?; - self.bpl = reader.read_uint16()?; - - reader.seek(SeekFrom::Current(19))?; // Skip empty bytes + // 0 = 16-byte header (Amp), 1 = 32-byte header + // TODO: Validate as 0 or 1 + let byte_1 = reader.read_uint8()?; + + if byte_1 == 0 { + // Load as amp texture (no mip maps) + self.bpp = reader.read_uint8()?; + self.encoding = reader.read_uint16()? as u32; + self.mip_maps = 0u8; + + self.width = reader.read_uint16()?; + self.height = reader.read_uint16()?; + self.bpl = reader.read_uint16()?; + + reader.seek(SeekFrom::Current(6))?; // Skip empty bytes + } else { + self.bpp = reader.read_uint8()?; + self.encoding = reader.read_uint32()?; + self.mip_maps = reader.read_uint8()?; + + self.width = reader.read_uint16()?; + self.height = reader.read_uint16()?; + self.bpl = reader.read_uint16()?; + + reader.seek(SeekFrom::Current(19))?; // Skip empty bytes + } // TODO: Calculate expected data size and verify against actual let current_pos = reader.pos(); From 1ccadef3a055ef9d816404ab1232c7079513530e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 20:56:50 -0400 Subject: [PATCH 077/113] Update nightly target version --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7caa73ea..37046e0d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2023-02-16" # 1.69.0 +channel = "nightly-2023-06-11" # 1.72.0 From 91e1dbac426ffe6d5a3f67e297542eef3b94dae3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 21:48:33 -0400 Subject: [PATCH 078/113] Fix determining platform type from amp milos --- core/grim/src/system.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/grim/src/system.rs b/core/grim/src/system.rs index f32c0b22..ba7c9b87 100644 --- a/core/grim/src/system.rs +++ b/core/grim/src/system.rs @@ -34,6 +34,7 @@ impl Platform { Some("milo_ps3") => Platform::PS3, Some("milo_wii") => Platform::Wii, Some("milo_xbox") => Platform::X360, + Some("rnd") => Platform::PS2, Some("rnd_ps2") => Platform::PS2, Some("gh") => Platform::PS2, _ => Platform::X360, From 325ad4a48af2d4b1425d8d3555f6366d35c6a0b2 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 21:50:20 -0400 Subject: [PATCH 079/113] Expose compression module --- core/grim/src/io/compression.rs | 13 +++++++++++++ core/grim/src/io/mod.rs | 2 +- core/grim/src/texture/io.rs | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/grim/src/io/compression.rs b/core/grim/src/io/compression.rs index dab3c44f..0a8c6961 100644 --- a/core/grim/src/io/compression.rs +++ b/core/grim/src/io/compression.rs @@ -45,6 +45,19 @@ pub fn inflate_gzip_block(data: &[u8], buffer: &mut [u8]) -> Result, Box Ok(inflated_data) } +pub fn inflate_gzip_block_no_buffer(data: &[u8]) -> Result, Box> { + if data.is_empty() { + // Fast exit + return Ok(Vec::new()); + } + + let mut buffer = Vec::new(); + let mut decoder = GzDecoder::new(data); + decoder.read_to_end(&mut buffer)?; + + Ok(buffer) +} + pub fn deflate_zlib_block(data: &[u8], buffer: &mut [u8]) -> Result, Box> { let mut compressor = Compress::new(Compression::best(), false); let status = compressor.compress(data, buffer, FlushCompress::Finish)?; diff --git a/core/grim/src/io/mod.rs b/core/grim/src/io/mod.rs index b9bbc53f..fccfc861 100644 --- a/core/grim/src/io/mod.rs +++ b/core/grim/src/io/mod.rs @@ -5,7 +5,7 @@ mod file; mod stream; pub use self::archive::*; -//pub(crate) use self::compression::*; +pub use self::compression::*; pub use self::crypt::*; pub use self::file::*; pub use self::stream::*; diff --git a/core/grim/src/texture/io.rs b/core/grim/src/texture/io.rs index a3c3328d..cb3e8add 100644 --- a/core/grim/src/texture/io.rs +++ b/core/grim/src/texture/io.rs @@ -119,6 +119,7 @@ impl Bitmap { // Decode PS2 bitmap let mut rgba = vec![0u8; self.calc_rgba_size()]; decode_from_bitmap(self, info, &mut rgba[..])?; + return Ok(rgba); } else if info.platform == Platform::PS3 || info.platform == Platform::X360 { // Decode next gen texture From 00e9c0ace79b0829d61426e457d05bd29fa9af7e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 21:50:40 -0400 Subject: [PATCH 080/113] Hacky support for loading external textures --- apps/ui/preview_ui/src/main.rs | 7 ++ apps/ui/preview_ui/src/render/loader.rs | 8 ++- apps/ui/preview_ui/src/render/milo_entry.rs | 75 ++++++++++++++++++++- apps/ui/preview_ui/src/state.rs | 1 + 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs index c172cb46..b33e6da3 100644 --- a/apps/ui/preview_ui/src/main.rs +++ b/apps/ui/preview_ui/src/main.rs @@ -264,6 +264,7 @@ fn consume_app_events( }*/ let milo = state.milo.as_ref().unwrap(); + let milo_path = state.open_file_path.as_ref().unwrap(); let info = state.system_info.as_ref().unwrap(); // Render everything for now @@ -273,6 +274,7 @@ fn consume_app_events( &mut materials, &mut textures, milo, + milo_path, entry_name.to_owned(), info ); @@ -318,6 +320,9 @@ fn open_file( state: &mut ResMut, app_event_writer: &mut EventWriter, ) { + // Clear file path + state.open_file_path.take(); + let ext = file_path.extension().unwrap().to_str().unwrap(); if ext.contains("hdr") { @@ -330,6 +335,7 @@ fn open_file( state.root = Some(create_ark_tree(&ark)); state.ark = Some(ark); + state.open_file_path = Some(file_path.to_owned()); } } else if ext.contains("milo") || ext.contains("gh") @@ -343,6 +349,7 @@ fn open_file( state.milo = Some(milo); state.system_info = Some(info); + state.open_file_path = Some(file_path.to_owned()); //ev_update_state.send(AppEvent::RefreshMilo); diff --git a/apps/ui/preview_ui/src/render/loader.rs b/apps/ui/preview_ui/src/render/loader.rs index 75b356c0..f2e8faf6 100644 --- a/apps/ui/preview_ui/src/render/loader.rs +++ b/apps/ui/preview_ui/src/render/loader.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; pub struct MiloLoader<'a> { milo: &'a ObjectDir, + milo_path: &'a std::path::Path, objects: HashMap<&'a str, &'a Object>, groups: HashMap<&'a str, &'a GroupObject>, mats: HashMap<&'a str, &'a MatObject>, @@ -25,7 +26,7 @@ pub enum TextureEncoding { } impl<'a> MiloLoader<'a> { - pub fn new(milo: &ObjectDir) -> MiloLoader { + pub fn new(milo: &'a ObjectDir, milo_path: &'a std::path::Path) -> MiloLoader<'a> { let entries = milo.get_entries(); let objects = entries @@ -74,6 +75,7 @@ impl<'a> MiloLoader<'a> { MiloLoader { milo, + milo_path, objects, groups, mats, @@ -129,6 +131,10 @@ impl<'a> MiloLoader<'a> { .get(name) .and_then(|o| Some(*o)) } + + pub fn get_milo_path(&self) -> &std::path::Path { + self.milo_path + } } fn get_objects_mapped(objects: &Vec, filter: impl Fn(&Object) -> Option<&T>) -> HashMap<&str, &T> { diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs index 69984d1f..42a22408 100644 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ b/apps/ui/preview_ui/src/render/milo_entry.rs @@ -8,6 +8,7 @@ use log::warn; use std::collections::HashMap; use std::error::Error; use std::fs; +use std::io::Read; use std::num::NonZeroU8; use std::path::{Path, PathBuf}; use thiserror::Error; @@ -26,10 +27,11 @@ pub fn render_milo_entry( materials: &mut ResMut>, bevy_textures: &mut ResMut>, milo: &ObjectDir, + milo_path: &Path, milo_entry: Option, system_info: &SystemInfo, ) { - let mut loader = MiloLoader::new(milo); + let mut loader = MiloLoader::new(milo, milo_path); // Get meshes for single object or return all meshes // TODO: Make less hacky @@ -345,10 +347,79 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in return loader.get_cached_texture(tex_name); } + let mut ext_tex = None; + // Get bitmap and decode texture // TODO: Check for external textures loader.get_texture(tex_name) - .and_then(|t| t.bitmap.as_ref()) + .and_then(|t| { + if t.bitmap.is_some() { + t.bitmap.as_ref() + } else { + // Load external texture + let milo_dir_path = loader.get_milo_path().parent().unwrap(); + + // Insert "gen" sub folder + // TODO: Support loading from milo gen folder too + let ext_img_path = match t.ext_path.rfind('/') { + Some(_) => todo!("Support external textures in nested relative path"), // Use [..] + None => milo_dir_path.join("gen").join(&t.ext_path), + }; + + let ext_img_file_stem = ext_img_path.file_stem().and_then(|fs| fs.to_str()).unwrap(); + let ext_img_path_dir = ext_img_path.parent().unwrap(); + + let files = ext_img_path_dir.find_files_with_depth(FileSearchDepth::Immediate).unwrap(); + + // TODO: Do case-insensitive compare + let matching_file = files + .iter() + .find(|f| f + .file_stem() + //.is_some_and(|fs| fs.eq_ignore_ascii_case(ext_img_file_stem)) + .and_then(|fs| fs.to_str()) + .is_some_and(|fs| fs.starts_with(ext_img_file_stem)) + ); + + if let Some(file_path) = matching_file { + log::info!("Found external texture file!\n\t{file_path:?}"); + + let data = if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("gz")) { + // File is gz compressed + let mut file = std::fs::File::open(file_path).unwrap(); + + // Read to buffer + let mut file_data = Vec::new(); + file.read_to_end(&mut file_data).unwrap(); + + // Inflate + grim::io::inflate_gzip_block_no_buffer(&file_data).unwrap() + } else { + let mut file = std::fs::File::open(file_path).unwrap(); + + // Read to buffer + let mut file_data = Vec::new(); + file.read_to_end(&mut file_data).unwrap(); + + file_data + }; + + let mut stream = grim::io::MemoryStream::from_slice_as_read(&data); + let bitmap = grim::texture::Bitmap::from_stream(&mut stream, system_info); + + if bitmap.is_ok() { + log::info!("Successfully opened bitmap"); + } else { + log::warn!("Error opening bitmap"); + } + + ext_tex = bitmap.ok(); + return ext_tex.as_ref(); + } + + None + } + }) .and_then(|b| match (system_info.platform, b.encoding) { (Platform::X360 | Platform::PS3, 8 | 24 | 32) => { let enc = match b.encoding { diff --git a/apps/ui/preview_ui/src/state.rs b/apps/ui/preview_ui/src/state.rs index 2a84bcf5..e4a36b66 100644 --- a/apps/ui/preview_ui/src/state.rs +++ b/apps/ui/preview_ui/src/state.rs @@ -22,6 +22,7 @@ pub struct AppState { pub root: Option, pub system_info: Option, pub milo: Option, + pub open_file_path: Option, pub settings_path: PathBuf, pub show_options: bool, pub pending_events: Vec, From 8f2c3ccc472172e7a9e15eb45957f4b60f91882d Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 12 Jun 2023 22:36:43 -0400 Subject: [PATCH 081/113] Support external textures for gltf export --- core/grim/src/model/export.rs | 89 +++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 7a64b2b9..d36c9a0b 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -686,6 +686,75 @@ impl GltfExporter { node_index } + fn load_external_texture(&self, tex: &Tex, system_info: &SystemInfo, milo_path: &Path) -> Option { + // TODO: Clean all this crap up + use std::io::Read; + + println!("{milo_path:?}"); + + // Load external texture + let milo_dir_path = milo_path.parent().unwrap(); + + // Insert "gen" sub folder + // TODO: Support loading from milo gen folder too + let ext_img_path = match tex.ext_path.rfind('/') { + Some(_) => todo!("Support external textures in nested relative path"), // Use [..] + None => milo_dir_path.join("gen").join(&tex.ext_path), + }; + + let ext_img_file_stem = ext_img_path.file_stem().and_then(|fs| fs.to_str()).unwrap(); + let ext_img_path_dir = ext_img_path.parent().unwrap(); + + let files = ext_img_path_dir.find_files_with_depth(FileSearchDepth::Immediate).unwrap(); + + // TODO: Do case-insensitive compare + let matching_file = files + .iter() + .find(|f| f + .file_stem() + //.is_some_and(|fs| fs.eq_ignore_ascii_case(ext_img_file_stem)) + .and_then(|fs| fs.to_str()) + .is_some_and(|fs| fs.starts_with(ext_img_file_stem)) + ); + + if let Some(file_path) = matching_file { + log::info!("Found external texture file!\n\t{file_path:?}"); + + let data = if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("gz")) { + // File is gz compressed + let mut file = std::fs::File::open(file_path).unwrap(); + + // Read to buffer + let mut file_data = Vec::new(); + file.read_to_end(&mut file_data).unwrap(); + + // Inflate + crate::io::inflate_gzip_block_no_buffer(&file_data).unwrap() + } else { + let mut file = std::fs::File::open(file_path).unwrap(); + + // Read to buffer + let mut file_data = Vec::new(); + file.read_to_end(&mut file_data).unwrap(); + + file_data + }; + + let mut stream = crate::io::MemoryStream::from_slice_as_read(&data); + let bitmap = crate::texture::Bitmap::from_stream(&mut stream, system_info); + + if bitmap.is_ok() { + log::info!("Successfully opened bitmap"); + } else { + log::warn!("Error opening bitmap"); + } + + return bitmap.ok(); + } + + None + } + fn process_textures(&self, gltf: &mut json::Root) -> HashMap { let mut image_indices = HashMap::new(); @@ -707,6 +776,7 @@ impl GltfExporter { .map(|(i, mt)| { let t = &mt.object; let sys_info = &mt.parent.info; + let milo_path = mt.parent.path.as_path(); // Remove .tex extension // TODO: Use more robust method @@ -721,17 +791,26 @@ impl GltfExporter { uri: { use base64::{Engine as _, engine::{self, general_purpose}, alphabet}; + let mut ext_tex = None; + // Decode image - let rgba = t.bitmap + let (rgba, (width, height)) = t.bitmap .as_ref() - .unwrap() - .unpack_rgba(sys_info) + .or_else(|| { + // Load external texture + ext_tex = self.load_external_texture(t, sys_info, milo_path); + ext_tex.as_ref() + }) + .map(|b| ( + b.unpack_rgba(sys_info).unwrap(), + (b.width as u32, b.height as u32) + )) .unwrap(); - let (width, height) = t.bitmap + /*let (width, height) = t.bitmap .as_ref() .map(|b| (b.width as u32, b.height as u32)) - .unwrap(); + .unwrap();*/ // Convert to png let png_data = crate::texture::write_rgba_to_vec(width, height, &rgba).unwrap(); From 5101c356037a2579b41d63ded2ab2226e7900d42 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 13 Jun 2023 22:36:09 -0400 Subject: [PATCH 082/113] Support loading antigrav meshes --- core/grim/src/scene/mesh/io.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index 64d18d73..f5b49476 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -17,11 +17,12 @@ pub enum MeshLoadError { fn is_version_supported(version: u32) -> bool { match version { 13 | 14 => true, // Amp Demo/Amp - 25 => true, // GH1 - 28 => true, // GH2/GH2 360 - 34 => true, // RB1/RB2 + 22 => true, // AntiGrav + 25 => true, // GH1 + 28 => true, // GH2/GH2 360 + 34 => true, // RB1/RB2 36 | 37 => true, // TBRB/GDRB - 38 => true, // RB3 + 38 => true, // RB3 _ => false } } @@ -165,8 +166,8 @@ impl ObjectReadWrite for MeshObject { let mut vec = Vert::default(); // TODO: Should probably clean up this loop - if version <= 14 { - // Amp (56 bytes) + if version <= 22 { + // Amp/AntiGrav (56 bytes) // Position vec.pos.x = reader.read_float32()?; vec.pos.y = reader.read_float32()?; @@ -346,6 +347,21 @@ impl ObjectReadWrite for MeshObject { let short_count = reader.read_uint32()?; reader.seek(SeekFrom::Current((short_count * 2) as i64 * std::mem::size_of::() as i64))?; + if version >= 22 { + // TODO: Preserve this data + let group_count = reader.read_uint32()?; + + for _ in 0..group_count { + reader.seek(SeekFrom::Current(4))?; // Some number + + let short_count = reader.read_uint32()?; + reader.seek(SeekFrom::Current(short_count as i64 * 2))?; + + let int_count = reader.read_uint32()?; + reader.seek(SeekFrom::Current(int_count as i64 * 4))?; + } + } + if version >= 14 { // Skip int reader.seek(SeekFrom::Current(4))?; From f7d489c717c5458920a01acdbe7a8fbe295e04e1 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 15 Jun 2023 20:47:26 -0400 Subject: [PATCH 083/113] Support loading antigrav mat + tex --- core/grim/src/scene/mat/io.rs | 8 +++++++- core/grim/src/scene/tex/io.rs | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/grim/src/scene/mat/io.rs b/core/grim/src/scene/mat/io.rs index fc7cd3f9..8c316c69 100644 --- a/core/grim/src/scene/mat/io.rs +++ b/core/grim/src/scene/mat/io.rs @@ -16,6 +16,7 @@ pub enum MatLoadError { fn is_version_supported(version: u32) -> bool { match version { 8 | 9 => true, // Amp Demo/Amp + 15 => true, // AntiGrav 21 => true, // GH1 25 | 27 | 28 => true, // GH2 4-song/GH2/GH2 360 41 | 47 => true, // RB1/RB2 @@ -38,7 +39,7 @@ impl ObjectReadWrite for MatObject { load_object(self, &mut reader, info)?; - // Amp/GH1 mats can be linked to many textures + // Amp/AntiGrav/GH1 mats can be linked to many textures if version <= 9 { // Read tex entries let tex_count = reader.read_uint32()?; @@ -87,6 +88,11 @@ impl ObjectReadWrite for MatObject { _ => continue, }; } + + if version <= 15 { + // AntiGrav - Skip remaining unknown crap + return Ok(()); + } } self.blend = reader.read_uint32()?.into(); diff --git a/core/grim/src/scene/tex/io.rs b/core/grim/src/scene/tex/io.rs index be61c106..bfb8193b 100644 --- a/core/grim/src/scene/tex/io.rs +++ b/core/grim/src/scene/tex/io.rs @@ -21,7 +21,8 @@ impl Tex { // Amp/GH1 10 => match magic { 5 => true, // Amp - 8 => true, + 7 => true, // AntiGrav + 8 => true, // GH1 _ => false }, // GH2 @@ -91,7 +92,13 @@ impl ObjectReadWrite for Tex { reader.read_boolean()?; } - self.use_ext_path = reader.read_boolean()?; + self.use_ext_path = if magic != 7 { + reader.read_boolean()? + } else { + // AntiGrav - Interpret 32-bit int as bool + let bool_int = reader.read_uint32()?; + bool_int != 0 + }; if reader.pos() == reader.len()? as u64 { return Ok(()); From 49ae2b17b18d3fc4a395fc75d938bb8bb15baec5 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 15 Jun 2023 22:41:24 -0400 Subject: [PATCH 084/113] Load external antigrav textures in preview ui --- apps/ui/preview_ui/src/render/loader.rs | 26 +++++++++-- apps/ui/preview_ui/src/render/milo_entry.rs | 52 ++++++++++++++------- core/grim/src/io/compression.rs | 15 +++++- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/apps/ui/preview_ui/src/render/loader.rs b/apps/ui/preview_ui/src/render/loader.rs index f2e8faf6..2bfdf2e8 100644 --- a/apps/ui/preview_ui/src/render/loader.rs +++ b/apps/ui/preview_ui/src/render/loader.rs @@ -14,7 +14,7 @@ pub struct MiloLoader<'a> { mats: HashMap<&'a str, &'a MatObject>, meshes: HashMap<&'a str, &'a MeshObject>, textures: HashMap<&'a str, &'a Tex>, - cached_textures: HashMap<&'a str, (&'a Tex, Vec, TextureEncoding)>, + cached_textures: HashMap<&'a str, (&'a Tex, Vec, ImageInfo)>, transforms: HashMap<&'a str, &'a dyn Trans>, } @@ -25,6 +25,24 @@ pub enum TextureEncoding { ATI2 } +pub struct ImageInfo { + pub width: u32, + pub height: u32, + pub mips: u32, + pub encoding: TextureEncoding, +} + +impl From<&grim::texture::Bitmap> for ImageInfo { + fn from(b: &grim::texture::Bitmap) -> Self { + Self { + width: b.width as u32, + height: b.height as u32, + mips: b.mip_maps as u32, + encoding: TextureEncoding::RGBA, // Actually set elsewhere + } + } +} + impl<'a> MiloLoader<'a> { pub fn new(milo: &'a ObjectDir, milo_path: &'a std::path::Path) -> MiloLoader<'a> { let entries = milo.get_entries(); @@ -116,14 +134,14 @@ impl<'a> MiloLoader<'a> { .and_then(|o| Some(*o)) } - pub fn get_cached_texture(&self, name: &str) -> Option<&(&'a Tex, Vec, TextureEncoding)> { + pub fn get_cached_texture(&self, name: &str) -> Option<&(&'a Tex, Vec, ImageInfo)> { self.cached_textures.get(name) } - pub fn set_cached_texture(&mut self, name: &str, rgba: Vec, encoding: TextureEncoding) { + pub fn set_cached_texture(&mut self, name: &str, rgba: Vec, image_info: ImageInfo) { let tex = self.get_texture(name).unwrap(); - self.cached_textures.insert(tex.get_name().as_str(), (tex, rgba, encoding)); + self.cached_textures.insert(tex.get_name().as_str(), (tex, rgba, image_info)); } pub fn get_transform(&self, name: &str) -> Option<&'a dyn Trans> { diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs index 42a22408..4ba5e469 100644 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ b/apps/ui/preview_ui/src/render/milo_entry.rs @@ -19,7 +19,7 @@ use grim::scene::{GroupObject, Matrix, MeshObject, Milo, MiloObject, Object, Obj use grim::texture::Bitmap; use crate::WorldMesh; -use super::{map_matrix, MiloLoader, TextureEncoding}; +use super::{ImageInfo, map_matrix, MiloLoader, TextureEncoding}; pub fn render_milo_entry( commands: &mut Commands, @@ -339,7 +339,7 @@ fn get_product_local_mat<'a>( map_matrix(milo_object.get_local_xfm()) } -fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_info: &SystemInfo) -> Option<&'b (&'a Tex, Vec, TextureEncoding)> { +fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_info: &SystemInfo) -> Option<&'b (&'a Tex, Vec, ImageInfo)> { // Check for cached texture if let Some(_cached) = loader.get_cached_texture(tex_name).take() { // TODO: Figure out why commented out line doesn't work (stupid lifetimes) @@ -360,9 +360,13 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in let milo_dir_path = loader.get_milo_path().parent().unwrap(); // Insert "gen" sub folder - // TODO: Support loading from milo gen folder too - let ext_img_path = match t.ext_path.rfind('/') { - Some(_) => todo!("Support external textures in nested relative path"), // Use [..] + // TODO: Support loading from milo gen folder too? + let ext_img_path = match t.ext_path.rfind("/") { + Some(last_slash_idx) => { + let (dir_path, file_name) = t.ext_path.split_at(last_slash_idx); + + milo_dir_path.join(dir_path).join("gen").join(&file_name[1..]) + }, None => milo_dir_path.join("gen").join(&t.ext_path), }; @@ -394,6 +398,16 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in // Inflate grim::io::inflate_gzip_block_no_buffer(&file_data).unwrap() + } else if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("z")) { + // File is zlib compressed + let mut file = std::fs::File::open(file_path).unwrap(); + + // Read to buffer + let mut file_data = Vec::new(); + file.read_to_end(&mut file_data).unwrap(); + + // Inflate + grim::io::inflate_deflate_block_no_buffer(&file_data).unwrap() } else { let mut file = std::fs::File::open(file_path).unwrap(); @@ -423,10 +437,10 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in .and_then(|b| match (system_info.platform, b.encoding) { (Platform::X360 | Platform::PS3, 8 | 24 | 32) => { let enc = match b.encoding { - 8 => TextureEncoding::DXT1, - 24 => TextureEncoding::DXT5, - 32 | _ => TextureEncoding::ATI2, - }; + 8 => TextureEncoding::DXT1, + 24 => TextureEncoding::DXT5, + 32 | _ => TextureEncoding::ATI2, + }; let mut data = b.raw_data.to_owned(); @@ -440,20 +454,26 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in } } - Some((data, enc)) + Some((data, ImageInfo { + encoding: enc, + ..b.into() + })) }, _ => b.unpack_rgba(system_info).ok() - .and_then(|rgba| Some((rgba, TextureEncoding::RGBA))) + .and_then(|rgba| Some((rgba, ImageInfo { + encoding: TextureEncoding::RGBA, + ..b.into() + }))) }) - .and_then(move |(rgba, enc)| { + .and_then(move |(rgba, image_info)| { // Cache decoded texture - loader.set_cached_texture(tex_name, rgba, enc); + loader.set_cached_texture(tex_name, rgba, image_info); loader.get_cached_texture(tex_name) }) } -fn map_texture<'a>(tex: &'a (&'a Tex, Vec, TextureEncoding)) -> Image { - let (tex, rgba, enc) = tex; +fn map_texture<'a>(tex: &'a (&'a Tex, Vec, ImageInfo)) -> Image { + let (tex, rgba, ImageInfo { width, height, mips: _, encoding: enc }) = tex; let bpp: usize = match enc { TextureEncoding::DXT1 => 4, @@ -461,7 +481,7 @@ fn map_texture<'a>(tex: &'a (&'a Tex, Vec, TextureEncoding)) -> Image { TextureEncoding::RGBA => 32, }; - let tex_size = ((tex.width as usize) * (tex.height as usize) * bpp) / 8; + let tex_size = ((*width as usize) * (*height as usize) * bpp) / 8; let use_mips = rgba.len() > tex_size; // TODO: Always support mips? let img_slice = if use_mips { diff --git a/core/grim/src/io/compression.rs b/core/grim/src/io/compression.rs index 0a8c6961..85e01003 100644 --- a/core/grim/src/io/compression.rs +++ b/core/grim/src/io/compression.rs @@ -1,7 +1,7 @@ //use flate2::{Compress, Decompress}; use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status}; -use flate2::read::GzDecoder; +use flate2::read::{DeflateDecoder, GzDecoder}; use std::error::Error; use std::io::Read; @@ -58,6 +58,19 @@ pub fn inflate_gzip_block_no_buffer(data: &[u8]) -> Result, Box Result, Box> { + if data.is_empty() { + // Fast exit + return Ok(Vec::new()); + } + + let mut buffer = Vec::new(); + let mut decoder = DeflateDecoder::new(data); + decoder.read_to_end(&mut buffer)?; + + Ok(buffer) +} + pub fn deflate_zlib_block(data: &[u8], buffer: &mut [u8]) -> Result, Box> { let mut compressor = Compress::new(Compression::best(), false); let status = compressor.compress(data, buffer, FlushCompress::Finish)?; From 97c2fc9345172123bd1a3ccda6d44cf141456742 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 15 Jun 2023 23:18:55 -0400 Subject: [PATCH 085/113] Safely handle bsp volume meshes --- core/grim/src/scene/mesh/io.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index f5b49476..d0d9a0c4 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -12,6 +12,8 @@ pub enum MeshLoadError { MeshVersionNotSupported { version: u32 }, + #[error("BSP volume geometry is not supported")] + BSPVolumeNotSupported, } fn is_version_supported(version: u32) -> bool { @@ -134,7 +136,8 @@ impl ObjectReadWrite for MeshObject { // Ignored but still validated let bsp = reader.read_uint8()?; if bsp != 0 { - panic!("Expected bsp field to be 0, not \"{}\" in Mesh", bsp); + //panic!("Expected bsp field to be 0, not \"{}\" in Mesh", bsp); + return Err(Box::new(MeshLoadError::BSPVolumeNotSupported)); } } From 94f4db3ceaea5e4f23656eb095fa10b92ddf931d Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 16 Jun 2023 22:22:21 -0400 Subject: [PATCH 086/113] Fix gzip inflation --- core/grim/src/io/compression.rs | 35 ++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/core/grim/src/io/compression.rs b/core/grim/src/io/compression.rs index 85e01003..ac926327 100644 --- a/core/grim/src/io/compression.rs +++ b/core/grim/src/io/compression.rs @@ -36,8 +36,17 @@ pub fn inflate_gzip_block(data: &[u8], buffer: &mut [u8]) -> Result, Box return Ok(Vec::new()); } + let mut inflate_size = 0; let mut decoder = GzDecoder::new(data); - let inflate_size = decoder.read(buffer)?; + + loop { + let read_size = decoder.read(&mut buffer[inflate_size..])?; + if read_size == 0 { + break; + } + + inflate_size += read_size; + } let mut inflated_data = vec![0u8; inflate_size]; inflated_data.clone_from_slice(&buffer[..inflate_size]); @@ -58,6 +67,30 @@ pub fn inflate_gzip_block_no_buffer(data: &[u8]) -> Result, Box Result, Box> { + if data.is_empty() { + // Fast exit + return Ok(Vec::new()); + } + + let mut inflate_size = 0; + let mut decoder = DeflateDecoder::new(data); + + loop { + let read_size = decoder.read(&mut buffer[inflate_size..])?; + if read_size == 0 { + break; + } + + inflate_size += read_size; + } + + let mut inflated_data = vec![0u8; inflate_size]; + inflated_data.clone_from_slice(&buffer[..inflate_size]); + + Ok(inflated_data) +} + pub fn inflate_deflate_block_no_buffer(data: &[u8]) -> Result, Box> { if data.is_empty() { // Fast exit From 106c6beda03a29441b2d6cad220e4af560f53148 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 16 Jun 2023 22:23:09 -0400 Subject: [PATCH 087/113] Support loading amp groups --- core/grim/src/scene/group/io.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/core/grim/src/scene/group/io.rs b/core/grim/src/scene/group/io.rs index 34dd804b..d0ef0d7d 100644 --- a/core/grim/src/scene/group/io.rs +++ b/core/grim/src/scene/group/io.rs @@ -16,6 +16,7 @@ pub enum GroupLoadError { fn is_version_supported(version: u32) -> bool { match version { + 4 => true, // Amp 7 => true, // GH1 11 => true, // GH2 4-song 12 => true, // GH2/GH2 360 @@ -42,11 +43,13 @@ impl ObjectReadWrite for GroupObject { load_draw(self, &mut reader, info, false)?; self.objects.clear(); - if version >= 11 { + if version > 10 { let object_count = reader.read_uint32()?; for _ in 0..object_count { self.objects.push(reader.read_prefixed_string()?); } + + self.environ = reader.read_prefixed_string()?; } else { // Copy anim/draw/trans objects from legacy version let mut obj_strings = HashSet::new(); @@ -68,7 +71,23 @@ impl ObjectReadWrite for GroupObject { } } - self.environ = reader.read_prefixed_string()?; + if version > 11 { + self.lod = reader.read_prefixed_string()?; + self.lod_screen_size = reader.read_float32()?; + } else if version == 4 { + reader.seek(SeekFrom::Current(4))?; + + let object_count = reader.read_uint32()?; + for _ in 0..object_count { + self.objects.push(reader.read_prefixed_string()?); + } + + // Unknown - Matches group name + reader.read_prefixed_string()?; + } else if version == 7 { + // Unknown - Matches group name + reader.read_prefixed_string()?; + } if version == 11 { // Demo doesn't have lod data for some reason @@ -85,14 +104,8 @@ impl ObjectReadWrite for GroupObject { } else { self.lod_screen_size = 0.0; } - } else { - self.draw_only = reader.read_prefixed_string()?; - self.lod = reader.read_prefixed_string()?; - self.lod_screen_size = reader.read_float32()?; - - if version >= 14 { - self.sort_in_world = reader.read_boolean()?; - } + } else if version >= 14 { + self.sort_in_world = reader.read_boolean()?; } Ok(()) From d50a61e36f7167f003dd38b541df2eaa195ed4b3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 16 Jun 2023 22:39:27 -0400 Subject: [PATCH 088/113] Use last texture for diffuse tex in amp mat --- core/grim/src/scene/mat/io.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/grim/src/scene/mat/io.rs b/core/grim/src/scene/mat/io.rs index 8c316c69..d38f5668 100644 --- a/core/grim/src/scene/mat/io.rs +++ b/core/grim/src/scene/mat/io.rs @@ -44,6 +44,8 @@ impl ObjectReadWrite for MatObject { // Read tex entries let tex_count = reader.read_uint32()?; + let mut max_order = 0; + for _ in 0..tex_count { let map_type = reader.read_uint32()?; @@ -52,12 +54,17 @@ impl ObjectReadWrite for MatObject { // Set name let name = reader.read_prefixed_string()?; - match map_type { + if map_type >= max_order { + max_order = map_type; + self.diffuse_tex = name; + } + + /*match map_type { 2 => self.emissive_map = name, - 4 => self.diffuse_tex = name, + 3 | 4 => self.diffuse_tex = name, 5 => self.environ_map = name, _ => continue, - }; + };*/ } // Skip remaining unknown crap From 273182d80f5b21fd08fb59140ab53f700ffa502a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 16 Jun 2023 23:15:49 -0400 Subject: [PATCH 089/113] Speedup image io --- apps/ui/preview_ui/src/render/milo_entry.rs | 35 ++++++++++++--------- core/grim/src/io/compression.rs | 20 +++++++++++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs index 4ba5e469..3d973d4a 100644 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ b/apps/ui/preview_ui/src/render/milo_entry.rs @@ -86,20 +86,22 @@ pub fn render_milo_entry( let mut bevy_mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList); - let mut positions = Vec::new(); - let mut normals = Vec::new(); - let mut tangents = Vec::new(); - let mut uvs = Vec::new(); + let vert_count = mesh.get_vertices().len(); - for vert in mesh.get_vertices() { - positions.push([vert.pos.x, vert.pos.y, vert.pos.z]); + let mut positions = vec![Default::default(); vert_count]; + let mut normals = vec![Default::default(); vert_count]; + let mut tangents = vec![Default::default(); vert_count]; + let mut uvs = vec![Default::default(); vert_count]; + + for (i, vert) in mesh.get_vertices().iter().enumerate() { + positions[i] = [vert.pos.x, vert.pos.y, vert.pos.z]; // TODO: Figure out normals/tangents //normals.push([vert.normals.x, vert.normals.y, vert.normals.z]); - normals.push([1.0, 1.0, 1.0]); - tangents.push([0.0, 0.0, 0.0, 1.0]); + normals[i] = [1.0, 1.0, 1.0]; + tangents[i] = [0.0, 0.0, 0.0, 1.0]; - uvs.push([vert.uv.u, vert.uv.v]); + uvs[i] = [vert.uv.u, vert.uv.v]; } let indices = bevy::render::mesh::Indices::U16( @@ -390,30 +392,33 @@ fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_in let data = if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("gz")) { // File is gz compressed + let file_size = grim::io::get_file_size(file_path) as usize; let mut file = std::fs::File::open(file_path).unwrap(); // Read to buffer - let mut file_data = Vec::new(); - file.read_to_end(&mut file_data).unwrap(); + let mut file_data = vec![0u8; file_size]; + file.read_exact(&mut file_data).unwrap(); // Inflate grim::io::inflate_gzip_block_no_buffer(&file_data).unwrap() } else if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("z")) { // File is zlib compressed + let file_size = grim::io::get_file_size(file_path) as usize; let mut file = std::fs::File::open(file_path).unwrap(); // Read to buffer - let mut file_data = Vec::new(); - file.read_to_end(&mut file_data).unwrap(); + let mut file_data = vec![0u8; file_size]; + file.read_exact(&mut file_data).unwrap(); // Inflate grim::io::inflate_deflate_block_no_buffer(&file_data).unwrap() } else { + let file_size = grim::io::get_file_size(file_path) as usize; let mut file = std::fs::File::open(file_path).unwrap(); // Read to buffer - let mut file_data = Vec::new(); - file.read_to_end(&mut file_data).unwrap(); + let mut file_data = vec![0u8; file_size]; + file.read_exact(&mut file_data).unwrap(); file_data }; diff --git a/core/grim/src/io/compression.rs b/core/grim/src/io/compression.rs index ac926327..81c6dda7 100644 --- a/core/grim/src/io/compression.rs +++ b/core/grim/src/io/compression.rs @@ -60,8 +60,26 @@ pub fn inflate_gzip_block_no_buffer(data: &[u8]) -> Result, Box Date: Mon, 26 Jun 2023 22:14:24 -0400 Subject: [PATCH 090/113] Fix features --- apps/cli/scene_tool/Cargo.toml | 2 +- core/grim/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli/scene_tool/Cargo.toml b/apps/cli/scene_tool/Cargo.toml index 78e6577c..bd3773be 100644 --- a/apps/cli/scene_tool/Cargo.toml +++ b/apps/cli/scene_tool/Cargo.toml @@ -6,5 +6,5 @@ edition.workspace = true [dependencies] clap = { workspace = true } -grim = { workspace = true, features = [ "model" ] } +grim = { workspace = true, features = [ "midi", "model" ] } thiserror = { workspace = true } diff --git a/core/grim/Cargo.toml b/core/grim/Cargo.toml index 2ae4e27a..1d3a8147 100644 --- a/core/grim/Cargo.toml +++ b/core/grim/Cargo.toml @@ -10,7 +10,7 @@ bitstream-io = { version = "1.6.0", optional = true } ffmpeg-next = { version = "6.0.0", optional = true } flate2 = "1.0.25" fon = { version = "0.6.0", optional = true } -gltf = { version = "1.2.0", optional = true, features = [ "import", "names" ] } +gltf = { version = "1.2.0", optional = true, features = [ "extras", "import", "names" ] } gltf-json = { version = "1.2.0", optional = true, features = [ "names" ] } grim_macros = { path = "../grim_macros" } grim_traits = { path = "../grim_traits" } From b0773a919372f0e430d16fc1b82ac92ccff52ed3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 26 Jun 2023 22:18:52 -0400 Subject: [PATCH 091/113] Support extracting freq archives --- core/grim/src/io/archive.rs | 81 ++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/core/grim/src/io/archive.rs b/core/grim/src/io/archive.rs index 60bbe46d..4a6dc811 100644 --- a/core/grim/src/io/archive.rs +++ b/core/grim/src/io/archive.rs @@ -10,6 +10,7 @@ use thiserror::Error as ThisError; const MAX_BLOCK_SIZE: usize = 0x20000; const ADDE_PADDING: [u8; 4] = [0xAD, 0xDE, 0xAD, 0xDE]; +const GZIP_MAGIC: u32 = u32::from_le_bytes([0x1F, 0x8B, 0x08, 0x08]); #[derive(Copy, Clone, Debug)] pub enum BlockType @@ -40,6 +41,7 @@ impl BlockInfo { #[derive(Debug)] pub enum MiloArchiveStructure { Blocked(BlockInfo), + GZIP, Uncompressed, } @@ -54,7 +56,9 @@ pub enum MiloBlockStructureError { #[error("Unsupported compression with magic of 0x{magic:X}")] UnsupportedCompression { magic: u32 - } + }, + #[error("UnknownIOError")] // TODO: Enclose IOError + IOError } #[derive(Debug, ThisError)] @@ -73,9 +77,22 @@ impl MiloArchive { let mut structure: MiloArchiveStructure = MiloArchiveStructure::Uncompressed; // TODO: Handle in else case let mut uncompressed: Vec = Vec::new(); - if let Some(block_type) = MiloArchive::get_block_type_or_none(&mut reader)? { + let block_result = MiloArchive::get_block_type_or_none(&mut reader); + + if let Err(MiloBlockStructureError::UnsupportedCompression { magic }) = block_result { + reader.seek(SeekFrom::Current(-4))?; + + if magic == GZIP_MAGIC { + let mut data = vec![0u8; reader.len()?]; + reader.read_bytes_into_slice(&mut data)?; + + uncompressed = inflate_gzip_block_no_buffer(&data)?; + } else { + return Err(Box::new(block_result.unwrap_err())); + } + } else if let Ok(Some(block_type)) = block_result { let mut block_info = BlockInfo::new(); - + block_info.block_type = block_type; block_info.start_offset = reader.read_uint32()?; @@ -119,8 +136,9 @@ impl MiloArchive { }) } - fn get_block_type_or_none(reader: &mut BinaryStream) -> Result, Box> { - let magic = reader.read_uint32()?; + fn get_block_type_or_none(reader: &mut BinaryStream) -> Result, MiloBlockStructureError> { + let magic = reader.read_uint32() + .map_err(|_| MiloBlockStructureError::IOError)?; match magic { 0xCABEDEAF => Ok(Some(BlockType::TypeA)), @@ -128,7 +146,7 @@ impl MiloArchive { 0xCCBEDEAF => Ok(Some(BlockType::TypeC)), 0xCDBEDEAF => Ok(Some(BlockType::TypeD)), // TODO: Assume uncompressed archive, or gzip then check version - _ => Err(Box::new(MiloBlockStructureError::UnsupportedCompression { magic })) + _ => Err(MiloBlockStructureError::UnsupportedCompression { magic }) } } @@ -169,18 +187,38 @@ impl MiloArchive { let mut packed_entries: Vec = Vec::new(); // Parse entry types + names - for _ in 0..entry_count { - let mut entry_type = reader.read_prefixed_string()?; - let entry_name = reader.read_prefixed_string()?; - - // Update class name - ObjectDir::fix_class_name(version, &mut entry_type); - - packed_entries.push(PackedObject { - name: entry_name, - object_type: entry_type, - data: Vec::new() - }) + if version <= 6 { + // Read as null-terminated strings + for _ in 0..entry_count { + let mut entry_type = reader.read_null_terminated_string()?; + let entry_name = reader.read_null_terminated_string()?; + + reader.seek(SeekFrom::Current(1))?; // Unknown, always 1? + + // Update class name + ObjectDir::fix_class_name(version, &mut entry_type); + + packed_entries.push(PackedObject { + name: entry_name, + object_type: entry_type, + data: Vec::new() + }) + } + } else { + // Read as size-prefixed strings + for _ in 0..entry_count { + let mut entry_type = reader.read_prefixed_string()?; + let entry_name = reader.read_prefixed_string()?; + + // Update class name + ObjectDir::fix_class_name(version, &mut entry_type); + + packed_entries.push(PackedObject { + name: entry_name, + object_type: entry_type, + data: Vec::new() + }) + } } if version == 10 { @@ -191,7 +229,7 @@ impl MiloArchive { for _ in 0..ext_count { reader.read_prefixed_string()?; } - } else { + } else if version > 10 { // TODO: Parse directory info (entry) /*let entry_size = self.guess_entry_size(&mut reader)?.unwrap(); reader.seek(SeekFrom::Current((entry_size + 4) as i64))?;*/ @@ -503,7 +541,10 @@ impl MiloArchive { writer.write_uint32(*size as u32)?; } }, - MiloArchiveStructure::Uncompressed => { + MiloArchiveStructure::GZIP => { + todo!("Gzip compression for milo archive not supported") + } + MiloArchiveStructure::Uncompressed => { // Write uncompressed data writer.write_bytes(&self.data[..])?; } From 816ce8f4f0ce7f686e4ef7ec48818b2ca25dc5d9 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 27 Jun 2023 21:11:03 -0400 Subject: [PATCH 092/113] Remove model feature from scene tool --- apps/cli/scene_tool/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli/scene_tool/Cargo.toml b/apps/cli/scene_tool/Cargo.toml index bd3773be..833adbb1 100644 --- a/apps/cli/scene_tool/Cargo.toml +++ b/apps/cli/scene_tool/Cargo.toml @@ -6,5 +6,5 @@ edition.workspace = true [dependencies] clap = { workspace = true } -grim = { workspace = true, features = [ "midi", "model" ] } +grim = { workspace = true, features = [ "midi" ] } thiserror = { workspace = true } From beab90ed841987c014d048bfe758dcbcc581766a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 27 Jun 2023 23:12:17 -0400 Subject: [PATCH 093/113] Support multi dotted file extensions in preview_ui --- apps/ui/preview_ui/src/main.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs index b33e6da3..7fa43d5e 100644 --- a/apps/ui/preview_ui/src/main.rs +++ b/apps/ui/preview_ui/src/main.rs @@ -323,8 +323,17 @@ fn open_file( // Clear file path state.open_file_path.take(); - let ext = file_path.extension().unwrap().to_str().unwrap(); - + // Get full file extension + let ext = file_path + .file_name() + .and_then(|n| n.to_str()) + .map(|n| match n.find('.') { + Some(i) => &n[i..], + _ => n + }) + .unwrap(); + + // TODO: Make case-insensitive if ext.contains("hdr") { // Open ark info!("Opening hdr from \"{}\"", file_path.display()); From c382f1f206d46d06ab141ca9fc989c048c4223d0 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 28 Jun 2023 00:16:42 -0400 Subject: [PATCH 094/113] Support freq meshes --- core/grim/src/scene/draw/io.rs | 12 +++++-- core/grim/src/scene/mesh/io.rs | 58 +++++++++++++++++++++++++++------ core/grim/src/scene/trans/io.rs | 2 +- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/core/grim/src/scene/draw/io.rs b/core/grim/src/scene/draw/io.rs index 2a6c240a..327fa2a4 100644 --- a/core/grim/src/scene/draw/io.rs +++ b/core/grim/src/scene/draw/io.rs @@ -15,7 +15,7 @@ pub enum DrawLoadError { fn is_version_supported(version: u32) -> bool { match version { - 0 => true, // Amp Demo/Amp + 0 => true, // Freq/Amp Demo/Amp 1 => true, // GH1 3 | 4 => true, // TBRB/GDRB _ => false @@ -44,6 +44,14 @@ pub(crate) fn load_draw(draw: &mut T, reader: &mut Box, i })); } + // Read as null-terminated or prefixed string depending on version + // Need to check sys_info because freq draw version is same as amp + let read_string = if info.version <= 6 { + |reader: &mut Box| reader.read_null_terminated_string() + } else { + |reader: &mut Box| reader.read_prefixed_string() + }; + if read_meta { load_object(draw, reader, info)?; } @@ -57,7 +65,7 @@ pub(crate) fn load_draw(draw: &mut T, reader: &mut Box, i // Reads draw objects let draw_count = reader.read_uint32()?; for _ in 0..draw_count { - draw_objects.push(reader.read_prefixed_string()?); + draw_objects.push(read_string(reader)?); } } diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index d0d9a0c4..b80da1cb 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -18,6 +18,7 @@ pub enum MeshLoadError { fn is_version_supported(version: u32) -> bool { match version { + 10 => true, // Freq 13 | 14 => true, // Amp Demo/Amp 22 => true, // AntiGrav 25 => true, // GH1 @@ -40,12 +41,19 @@ impl ObjectReadWrite for MeshObject { })); } + // Read as null-terminated or prefixed string depending on version + let read_string = if version <= 10 { + |reader: &mut Box| reader.read_null_terminated_string() + } else { + |reader: &mut Box| reader.read_prefixed_string() + }; + load_object(self, &mut reader, info)?; load_trans(self, &mut reader, info, false)?; load_draw(self, &mut reader, info, false)?; if version < 15 { - _ = reader.read_prefixed_string()?; // Some string + _ = reader.seek(SeekFrom::Current(4)); // Always 0? // Read bones self.bones.clear(); @@ -54,7 +62,7 @@ impl ObjectReadWrite for MeshObject { for _ in 0..bone_count { let mut bone = BoneTrans::default(); - bone.name = reader.read_prefixed_string()?; + bone.name = read_string(&mut reader)?; self.bones.push(bone); } } @@ -69,28 +77,28 @@ impl ObjectReadWrite for MeshObject { reader.seek(SeekFrom::Current(4))?; } - self.mat = reader.read_prefixed_string()?; + self.mat = read_string(&mut reader)?; if version == 27 { // Secondary material? _ = reader.read_prefixed_string()?; } - self.geom_owner = reader.read_prefixed_string()?; + self.geom_owner = read_string(&mut reader)?; if version < 13 { // Secondary geom owner? - _ = reader.read_prefixed_string()?; + _ = read_string(&mut reader)?; } if version < 15 { // Set on mesh instead of base trans object - let trans_parent = reader.read_prefixed_string()?; + let trans_parent = read_string(&mut reader)?; self.set_parent(trans_parent); } if version < 14 { // Skip empty RndTransformable strings - _ = reader.read_prefixed_string()?; - _ = reader.read_prefixed_string()?; + _ = read_string(&mut reader)?; + _ = read_string(&mut reader)?; } if version < 3 { @@ -110,7 +118,7 @@ impl ObjectReadWrite for MeshObject { if version < 15 { // Skip unknown string + float - _ = reader.read_prefixed_string()?; + _ = read_string(&mut reader)?; reader.seek(SeekFrom::Current(4))?; } @@ -169,7 +177,37 @@ impl ObjectReadWrite for MeshObject { let mut vec = Vert::default(); // TODO: Should probably clean up this loop - if version <= 22 { + if version <= 10 { + // Freq (56 bytes) + // Position + vec.pos.x = reader.read_float32()?; + vec.pos.y = reader.read_float32()?; + vec.pos.z = reader.read_float32()?; + + // Normals + vec.normals.x = reader.read_float32()?; + vec.normals.y = reader.read_float32()?; + vec.normals.z = reader.read_float32()?; + + // UVs + vec.uv.u = reader.read_float32()?; + vec.uv.v = reader.read_float32()?; + + // Weights + vec.weights[0] = reader.read_float32()?; + vec.weights[1] = reader.read_float32()?; + vec.weights[2] = reader.read_float32()?; + vec.weights[3] = reader.read_float32()?; + + // Bone indices + vec.bones[0] = reader.read_uint16()?; + vec.bones[1] = reader.read_uint16()?; + vec.bones[2] = reader.read_uint16()?; + vec.bones[3] = reader.read_uint16()?; + + self.vertices.push(vec); + continue; + } else if version <= 22 { // Amp/AntiGrav (56 bytes) // Position vec.pos.x = reader.read_float32()?; diff --git a/core/grim/src/scene/trans/io.rs b/core/grim/src/scene/trans/io.rs index 9a191e70..2b7d626e 100644 --- a/core/grim/src/scene/trans/io.rs +++ b/core/grim/src/scene/trans/io.rs @@ -15,7 +15,7 @@ pub enum TransLoadError { fn is_version_supported(version: u32) -> bool { match version { - 5 => true, // Amp/Amp Demo + 5 => true, // Freq/Amp/Amp Demo 8 => true, // GH1 9 => true, // GH2/RB1/RB2/TBRB/GDRB _ => false From 8c3392e41f5ab4bad886a8e8901de1003fc59fa5 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 28 Jun 2023 00:23:13 -0400 Subject: [PATCH 095/113] Fix reading trans children for freq --- core/grim/src/scene/trans/io.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/grim/src/scene/trans/io.rs b/core/grim/src/scene/trans/io.rs index 2b7d626e..ef84b562 100644 --- a/core/grim/src/scene/trans/io.rs +++ b/core/grim/src/scene/trans/io.rs @@ -44,6 +44,14 @@ pub(crate) fn load_trans(trans: &mut T, reader: &mut Box })); } + // Read as null-terminated or prefixed string depending on version + // Need to check sys_info because freq trans version is same as amp + let read_string = if info.version <= 6 { + |reader: &mut Box| reader.read_null_terminated_string() + } else { + |reader: &mut Box| reader.read_prefixed_string() + }; + if read_meta { load_object(trans, reader, info)?; } @@ -58,7 +66,7 @@ pub(crate) fn load_trans(trans: &mut T, reader: &mut Box // Reads trans objects let trans_count = reader.read_uint32()?; for _ in 0..trans_count { - trans_objects.push(reader.read_prefixed_string()?); + trans_objects.push(read_string(reader)?); } } From 7870f2cd0c3800a5ddeb8fb115d9fe75a5bdf55a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 28 Jun 2023 20:55:50 -0400 Subject: [PATCH 096/113] Support gz for guessing platform --- core/grim/src/system.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/grim/src/system.rs b/core/grim/src/system.rs index ba7c9b87..49eb3efb 100644 --- a/core/grim/src/system.rs +++ b/core/grim/src/system.rs @@ -37,6 +37,7 @@ impl Platform { Some("rnd") => Platform::PS2, Some("rnd_ps2") => Platform::PS2, Some("gh") => Platform::PS2, + Some("gz") => Platform::PS2, _ => Platform::X360, }, None => Platform::X360, From 55f4cb86a046d259e6281da5096c1edf42f2567e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 3 Jul 2023 14:19:21 -0400 Subject: [PATCH 097/113] Update group load to match 010 template --- core/grim/src/scene/group/io.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/grim/src/scene/group/io.rs b/core/grim/src/scene/group/io.rs index d0ef0d7d..a51487ac 100644 --- a/core/grim/src/scene/group/io.rs +++ b/core/grim/src/scene/group/io.rs @@ -84,17 +84,15 @@ impl ObjectReadWrite for GroupObject { // Unknown - Matches group name reader.read_prefixed_string()?; + + // Zero'd numbers + reader.seek(SeekFrom::Current(8))?; } else if version == 7 { // Unknown - Matches group name reader.read_prefixed_string()?; } - if version == 11 { - // Demo doesn't have lod data for some reason - return Ok(()); - } - - if version <= 12 { + /*if version <= 12 { let lod_width = reader.read_float32()?; let lod_height = reader.read_float32()?; @@ -104,7 +102,9 @@ impl ObjectReadWrite for GroupObject { } else { self.lod_screen_size = 0.0; } - } else if version >= 14 { + }*/ + + if version > 13 { self.sort_in_world = reader.read_boolean()?; } From 4f25aaa79876899651db44b99d688a0fc18f97f5 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 7 Jul 2023 20:29:38 -0400 Subject: [PATCH 098/113] Support loading v3 ark --- core/grim/src/ark/io.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/grim/src/ark/io.rs b/core/grim/src/ark/io.rs index 3ebc78ad..9c35b324 100644 --- a/core/grim/src/ark/io.rs +++ b/core/grim/src/ark/io.rs @@ -193,14 +193,16 @@ impl Ark { part_start += size; } - // TODO: Verify count matches here too - let part_name_count = reader.read_uint32() - .map_err(|_| ArkReadError::ArkNotSupported)?; - - // Skip part file names - for _ in 0..part_name_count { - reader.read_prefixed_string() + if self.version >= 5 { + // TODO: Verify count matches here too + let part_name_count = reader.read_uint32() .map_err(|_| ArkReadError::ArkNotSupported)?; + + // Skip part file names + for _ in 0..part_name_count { + reader.read_prefixed_string() + .map_err(|_| ArkReadError::ArkNotSupported)?; + } } // Read string blob @@ -264,7 +266,7 @@ fn get_version(data: &[u8]) -> i32 { fn version_is_supported(version: i32) -> bool { match version { - 5 => true, + 3 | 5 => true, _ => false } } From 5cda599182e20a2be7112033b4000ea0324f0bf1 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 7 Jul 2023 23:18:09 -0400 Subject: [PATCH 099/113] Use generic stream parameter to open milo --- apps/cli/p9_scene_tool/src/apps/milo2midi.rs | 2 +- apps/cli/scene_tool/src/apps/milo2dir.rs | 2 +- apps/cli/scene_tool/src/apps/milo2kr.rs | 2 +- apps/cli/scene_tool/src/apps/savemilo.rs | 2 +- apps/ui/preview_ui/src/render/mod.rs | 2 +- core/grim/src/io/archive.rs | 3 +-- core/grim/src/model/export.rs | 2 +- utils/milo_export/src/helpers.rs | 2 +- utils/shared/src/lib.rs | 2 +- 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/cli/p9_scene_tool/src/apps/milo2midi.rs b/apps/cli/p9_scene_tool/src/apps/milo2midi.rs index 2f52aa94..5f26ed3c 100644 --- a/apps/cli/p9_scene_tool/src/apps/milo2midi.rs +++ b/apps/cli/p9_scene_tool/src/apps/milo2midi.rs @@ -47,7 +47,7 @@ impl SubApp for Milo2MidiApp { } // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let mut stream = FileStream::from_path_as_read_open(&milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // Unpack dir and entries diff --git a/apps/cli/scene_tool/src/apps/milo2dir.rs b/apps/cli/scene_tool/src/apps/milo2dir.rs index cb05787a..ba495308 100644 --- a/apps/cli/scene_tool/src/apps/milo2dir.rs +++ b/apps/cli/scene_tool/src/apps/milo2dir.rs @@ -75,7 +75,7 @@ impl SubApp for Milo2DirApp { println!("Opening {}", file_name); } - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(milo_path)?); + let mut stream = FileStream::from_path_as_read_open(milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // TODO: First get system info from args then guess if not supplied diff --git a/apps/cli/scene_tool/src/apps/milo2kr.rs b/apps/cli/scene_tool/src/apps/milo2kr.rs index 020a4652..f26e6de3 100644 --- a/apps/cli/scene_tool/src/apps/milo2kr.rs +++ b/apps/cli/scene_tool/src/apps/milo2kr.rs @@ -43,7 +43,7 @@ impl SubApp for Milo2KrApp { println!("Opening {}", file_name); } - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(milo_path)?); + let mut stream = FileStream::from_path_as_read_open(milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // TODO: First get system info from args then guess if not supplied diff --git a/apps/cli/scene_tool/src/apps/savemilo.rs b/apps/cli/scene_tool/src/apps/savemilo.rs index 9f648c06..4a8f4b4b 100644 --- a/apps/cli/scene_tool/src/apps/savemilo.rs +++ b/apps/cli/scene_tool/src/apps/savemilo.rs @@ -37,7 +37,7 @@ impl SubApp for SaveMiloApp { } // Open milo file - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(in_milo_path)?); + let mut stream = FileStream::from_path_as_read_open(in_milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // Guess platform info diff --git a/apps/ui/preview_ui/src/render/mod.rs b/apps/ui/preview_ui/src/render/mod.rs index 277bbd1e..2d74877c 100644 --- a/apps/ui/preview_ui/src/render/mod.rs +++ b/apps/ui/preview_ui/src/render/mod.rs @@ -22,7 +22,7 @@ use grim::scene::{RndMesh, Matrix, MeshObject, MiloObject, Object, ObjectDir, Pa pub fn open_and_unpack_milo>(milo_path: T) -> Result<(ObjectDir, SystemInfo), Box> { let milo_path = milo_path.as_ref(); - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(milo_path)?); + let mut stream = FileStream::from_path_as_read_open(milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; let system_info = SystemInfo::guess_system_info(&milo, &milo_path); diff --git a/core/grim/src/io/archive.rs b/core/grim/src/io/archive.rs index 4a6dc811..bc6a4513 100644 --- a/core/grim/src/io/archive.rs +++ b/core/grim/src/io/archive.rs @@ -70,8 +70,7 @@ pub enum MiloUnpackError { } impl MiloArchive { - pub fn from_stream(stream: &mut Box) -> Result> { - let stream = stream.as_mut(); + pub fn from_stream(stream: &mut T) -> Result> { let mut reader = BinaryStream::from_stream(stream); // Should always be little endian let mut structure: MiloArchiveStructure = MiloArchiveStructure::Uncompressed; // TODO: Handle in else case diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index d36c9a0b..41d98b56 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -371,7 +371,7 @@ impl GltfExporter { let milo_path: PathBuf = path.into(); // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let mut stream = FileStream::from_path_as_read_open(&milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // Guess system info and unpack dir + entries diff --git a/utils/milo_export/src/helpers.rs b/utils/milo_export/src/helpers.rs index 337f07e5..8f5405a5 100644 --- a/utils/milo_export/src/helpers.rs +++ b/utils/milo_export/src/helpers.rs @@ -425,7 +425,7 @@ fn try_open_milo(milo_path: &Path) -> Result<(SystemInfo, ObjectDir), Box Result<(SystemInfo, ObjectDir), Box> { // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let mut stream = FileStream::from_path_as_read_open(&milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // Unpack dir and entries diff --git a/utils/shared/src/lib.rs b/utils/shared/src/lib.rs index 7ce4e1d8..0c599338 100644 --- a/utils/shared/src/lib.rs +++ b/utils/shared/src/lib.rs @@ -14,7 +14,7 @@ pub struct MiloLoader { impl MiloLoader { pub fn from_path(milo_path: PathBuf) -> Result> { // Open milo - let mut stream: Box = Box::new(FileStream::from_path_as_read_open(&milo_path)?); + let mut stream = FileStream::from_path_as_read_open(&milo_path)?; let milo = MiloArchive::from_stream(&mut stream)?; // Unpack milo From 9a97621abbbe6ab8d28463f789178b7d5695f30d Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Fri, 7 Jul 2023 23:49:04 -0400 Subject: [PATCH 100/113] Support reading entry data from ark part --- core/grim/src/ark/ark.rs | 14 +++++++++++--- core/grim/src/ark/io.rs | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/core/grim/src/ark/ark.rs b/core/grim/src/ark/ark.rs index 9e896334..d0ce3d55 100644 --- a/core/grim/src/ark/ark.rs +++ b/core/grim/src/ark/ark.rs @@ -7,7 +7,8 @@ pub struct Ark { #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub version: i32, pub encryption: ArkEncryption, #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub entries: Vec, - pub path: PathBuf, // Hdr/ark path + pub path: PathBuf, // Hdr/ark path, + pub part_paths: Vec, } #[derive(Debug)] @@ -86,8 +87,15 @@ impl Ark { .find(|e| e.id == id) .expect("Invalid id"); - // TODO: Support reading from ark parts - let mut file = std::fs::File::open(&self.path)?; + // Open from main ark or ark part + let file_path = if self.version >= 3 && self.version <= 10 { + &self.part_paths[entry.part as usize] + } else { + &self.path + }; + + // TODO: Support reading from non-first ark part? + let mut file = std::fs::File::open(file_path)?; file.seek(SeekFrom::Start(entry.offset))?; let mut buffer = vec![0u8; entry.size]; diff --git a/core/grim/src/ark/io.rs b/core/grim/src/ark/io.rs index 9c35b324..e8d60971 100644 --- a/core/grim/src/ark/io.rs +++ b/core/grim/src/ark/io.rs @@ -72,6 +72,21 @@ impl Ark { None => ArkEncryption::None, }, path: path.to_owned(), + part_paths: { + let dir_path = path.parent().unwrap(); + let files = dir_path.find_files_with_depth(FileSearchDepth::Immediate).unwrap(); + + let mut ark_parts = files + .into_iter() + .filter(|f| f + .as_os_str() + .to_str() + .is_some_and(|e| e.ends_with(".ARK") || e.ends_with(".ark"))) + .collect::>(); + + ark_parts.sort_by(|a, b| a.cmp(b)); + ark_parts + }, ..Default::default() }; From 4675eff415a583319240c14d2983a2a6477193a6 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 9 Jul 2023 21:30:35 -0400 Subject: [PATCH 101/113] Add clone to bitmap --- core/grim/src/texture/bitmap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/grim/src/texture/bitmap.rs b/core/grim/src/texture/bitmap.rs index 8ed72761..d628b15f 100644 --- a/core/grim/src/texture/bitmap.rs +++ b/core/grim/src/texture/bitmap.rs @@ -2,7 +2,7 @@ use crate::{Platform, SystemInfo}; use crate::io::IOEndian; #[cfg(feature = "python")] use pyo3::prelude::*; -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "python", pyclass)] pub struct Bitmap { #[cfg_attr(feature = "pyo3", pyo3(get, set))] pub bpp: u8, From 3ec6c0c47681631007826a5e48bfd56860cae4c1 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 9 Jul 2023 23:28:25 -0400 Subject: [PATCH 102/113] Add band placer --- core/grim/src/scene/band_placer/io.rs | 59 ++++++++++++++++++++++++++ core/grim/src/scene/band_placer/mod.rs | 45 ++++++++++++++++++++ core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/object.rs | 5 +++ 4 files changed, 111 insertions(+) create mode 100644 core/grim/src/scene/band_placer/io.rs create mode 100644 core/grim/src/scene/band_placer/mod.rs diff --git a/core/grim/src/scene/band_placer/io.rs b/core/grim/src/scene/band_placer/io.rs new file mode 100644 index 00000000..2d832284 --- /dev/null +++ b/core/grim/src/scene/band_placer/io.rs @@ -0,0 +1,59 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum BandPlacerLoadError { + #[error("BandPlacer version {version} is not supported")] + BandPlacerVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 2 => true, // GH2/GH2 360 + _ => false + } +} + +impl ObjectReadWrite for BandPlacer { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + if !is_version_supported(version) { + return Err(Box::new(BandPlacerLoadError::BandPlacerVersionNotSupported { + version + })); + } + + load_object(self, &mut reader, info)?; + load_draw(self, &mut reader, info, false)?; + load_trans(self, &mut reader, info, false)?; + + self.center = reader.read_prefixed_string()?; + + Ok(()) + } + + fn save(&self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut stream = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 2; + + stream.write_uint32(version)?; + + save_object(self, &mut stream, info)?; + save_trans(self, &mut stream, info, false)?; + save_draw(self, &mut stream, info, false)?; + + stream.write_prefixed_string(&self.center)?; + + Ok(()) + } +} \ No newline at end of file diff --git a/core/grim/src/scene/band_placer/mod.rs b/core/grim/src/scene/band_placer/mod.rs new file mode 100644 index 00000000..e3519501 --- /dev/null +++ b/core/grim/src/scene/band_placer/mod.rs @@ -0,0 +1,45 @@ +mod io; + +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +#[milo] +#[milo_super(Draw, Trans)] +pub struct BandPlacer { + pub center: String, +} + +impl Default for BandPlacer { + fn default() -> Self { + // TODO: Match default values to C++ code + Self { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // Draw object + showing: true, + draw_objects: Vec::new(), + sphere: Sphere::default(), + draw_order: 0.0, + override_include_in_depth_only_pass: OverrideIncludeInDepthOnlyPass::default(), + + // Trans object + local_xfm: Matrix::default(), + world_xfm: Matrix::default(), + + trans_objects: Vec::new(), + + constraint: TransConstraint::default(), + target: String::default(), + + preserve_scale: false, + parent: String::default(), + + // BandPlacer object + center: String::default(), + } + } +} \ No newline at end of file diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index 9cbd8171..0caa24b1 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -1,4 +1,5 @@ mod anim; +mod band_placer; mod cam; mod char_bones_samples; mod char_clip; @@ -25,6 +26,7 @@ mod trans; mod trans_anim; pub use anim::*; +pub use band_placer::*; pub use cam::*; pub use char_clip::*; pub use char_bones_samples::*; diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index 3ef07d51..060fe57b 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -4,6 +4,7 @@ use crate::scene::*; pub enum Object { Anim(AnimObject), + BandPlacer(BandPlacer), Cam(CamObject), CharClipSamples(CharClipSamples), CharLipSync(CharLipSync), @@ -34,6 +35,7 @@ impl Object { pub fn get_name(&self) -> &str { match self { Object::Anim(anim) => &anim.name, + Object::BandPlacer(band_placer) => &band_placer.name, Object::Cam(cam) => &cam.name, Object::CharClipSamples(ccs) => &ccs.name, Object::CharLipSync(cls) => &cls.name, @@ -57,6 +59,7 @@ impl Object { pub fn get_type(&self) -> &str { match self { Object::Anim(_) => "Anim", + Object::BandPlacer(_) => "BandPlacer", Object::Cam(_) => "Cam", Object::CharClipSamples(_) => "CharClipSamples", Object::CharLipSync(_) => "CharLipSync", @@ -91,6 +94,7 @@ impl Object { let obj: &dyn ObjectReadWrite = match &self { Object::Anim(obj) => obj, + Object::BandPlacer(obj) => obj, Object::Cam(obj) => obj, Object::CubeTex(obj) => obj, Object::Draw(obj) => obj, @@ -127,6 +131,7 @@ impl Object { Object::Packed(packed) => { match packed.object_type.as_str() { "Anim" => unpack_object(packed, info).map(|o| Object::Anim(o)), + "BandPlacer" => unpack_object(packed, info).map(|o| Object::BandPlacer(o)), "Cam" => unpack_object(packed, info).map(|o| Object::Cam(o)), "CharClipSamples" => unpack_object(packed, info).map(|o| Object::CharClipSamples(o)), "CharLipSync" => unpack_object(packed, info).map(|o| Object::CharLipSync(o)), From e1d163c321fded840f0963b4b4408cf6059c9028 Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Mon, 10 Jul 2023 00:01:19 -0700 Subject: [PATCH 103/113] Add ColorPalette support --- core/grim/src/scene/color_palette/io.rs | 79 ++++++++++++++++++++++++ core/grim/src/scene/color_palette/mod.rs | 33 ++++++++++ core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/object.rs | 5 ++ 4 files changed, 119 insertions(+) create mode 100644 core/grim/src/scene/color_palette/io.rs create mode 100644 core/grim/src/scene/color_palette/mod.rs diff --git a/core/grim/src/scene/color_palette/io.rs b/core/grim/src/scene/color_palette/io.rs new file mode 100644 index 00000000..c746743a --- /dev/null +++ b/core/grim/src/scene/color_palette/io.rs @@ -0,0 +1,79 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use std::error::Error; +use thiserror::Error as ThisError; +use log::info; + +#[derive(Debug, ThisError)] +pub enum ColorPaletteLoadError { + #[error("ColorPalette version {version} is not supported")] + ColorPaletteVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 1 => true, // RB1 and up, I don't think a v2 exists + _ => false + } +} + +impl ObjectReadWrite for ColorPalette { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + if !is_version_supported(version) { + return Err(Box::new(ColorPaletteLoadError::ColorPaletteVersionNotSupported { + version + })); + } + + load_object(self, &mut reader, info)?; + + self.num_colors = reader.read_uint32()?; + self.colors = load_palette_colors(self.num_colors, &mut reader)?; + + Ok(()) + } + + fn save(&self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut stream = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 1; + + stream.write_uint32(version)?; + + save_object(self, &mut stream, info)?; + + stream.write_uint32(self.num_colors)?; + + for color in &self.colors { + stream.write_float32(color.red)?; + stream.write_float32(color.green)?; + stream.write_float32(color.blue)?; + stream.write_float32(color.unknown)?; + } + + Ok(()) + } +} + +fn load_palette_colors(num_colors: u32, reader: &mut Box,) -> Result, Box> { + let mut colors = Vec::new(); + + for _ in 0..num_colors { + let red = reader.read_float32()?; + let green = reader.read_float32()?; + let blue = reader.read_float32()?; + let unknown = reader.read_float32()?; + + colors.push(PaletteColor {red, green, blue, unknown, }) + } + + Ok(colors) +} \ No newline at end of file diff --git a/core/grim/src/scene/color_palette/mod.rs b/core/grim/src/scene/color_palette/mod.rs new file mode 100644 index 00000000..c5e70cab --- /dev/null +++ b/core/grim/src/scene/color_palette/mod.rs @@ -0,0 +1,33 @@ +mod io; + +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +pub struct PaletteColor { + pub red: f32, + pub green: f32, + pub blue: f32, + pub unknown: f32, // TODO: figure out what this is +} + +#[milo] +pub struct ColorPalette { + pub num_colors: u32, + pub colors: Vec, +} + +impl Default for ColorPalette { + fn default() -> Self { + Self { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // ColorPalette object + num_colors: 0, + colors: Vec::new(), + } + } +} \ No newline at end of file diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index 0caa24b1..fbb31df8 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -5,6 +5,7 @@ mod char_bones_samples; mod char_clip; mod char_clip_samples; mod char_lip_sync; +mod color_palette; mod cube_tex; mod draw; mod group; @@ -32,6 +33,7 @@ pub use char_clip::*; pub use char_bones_samples::*; pub use char_clip_samples::*; pub use char_lip_sync::*; +pub use color_palette::*; pub use cube_tex::*; pub use draw::*; pub use group::*; diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index 060fe57b..c69986b6 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -8,6 +8,7 @@ pub enum Object { Cam(CamObject), CharClipSamples(CharClipSamples), CharLipSync(CharLipSync), + ColorPalette(ColorPalette), CubeTex(CubeTexObject), Draw(DrawObject), Group(GroupObject), @@ -39,6 +40,7 @@ impl Object { Object::Cam(cam) => &cam.name, Object::CharClipSamples(ccs) => &ccs.name, Object::CharLipSync(cls) => &cls.name, + Object::ColorPalette(color_palette) => &color_palette.name, Object::CubeTex(cube) => &cube.name, Object::Draw(draw) => &draw.name, Object::Group(grp) => &grp.name, @@ -63,6 +65,7 @@ impl Object { Object::Cam(_) => "Cam", Object::CharClipSamples(_) => "CharClipSamples", Object::CharLipSync(_) => "CharLipSync", + Object::ColorPalette(_) => "ColorPalette", Object::CubeTex(_) => "CubeTex", Object::Draw(_) => "Draw", Object::Group(_) => "Group", @@ -96,6 +99,7 @@ impl Object { Object::Anim(obj) => obj, Object::BandPlacer(obj) => obj, Object::Cam(obj) => obj, + Object::ColorPalette(obj) => obj, Object::CubeTex(obj) => obj, Object::Draw(obj) => obj, Object::Group(obj) => obj, @@ -135,6 +139,7 @@ impl Object { "Cam" => unpack_object(packed, info).map(|o| Object::Cam(o)), "CharClipSamples" => unpack_object(packed, info).map(|o| Object::CharClipSamples(o)), "CharLipSync" => unpack_object(packed, info).map(|o| Object::CharLipSync(o)), + "ColorPalette" => unpack_object(packed, info).map(|o| Object::ColorPalette(o)), "CubeTex" => unpack_object(packed, info).map(|o| Object::CubeTex(o)), "Draw" => unpack_object(packed, info).map(|o| Object::Draw(o)), "Group" => unpack_object(packed, info).map(|o| Object::Group(o)), From 1ef5455a7409ece2608d304256504a883702de82 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 15 Jul 2023 14:40:11 -0400 Subject: [PATCH 104/113] Stub char related objects --- core/grim/src/scene/band_character/mod.rs | 0 core/grim/src/scene/char_driver/mod.rs | 0 core/grim/src/scene/char_weightable/mod.rs | 0 core/grim/src/scene/character/mod.rs | 0 core/grim/src/scene/mod.rs | 12 ++++++++++++ core/grim/src/scene/object_dir_object/mod.rs | 0 core/grim/src/scene/rnd_dir/mod.rs | 0 7 files changed, 12 insertions(+) create mode 100644 core/grim/src/scene/band_character/mod.rs create mode 100644 core/grim/src/scene/char_driver/mod.rs create mode 100644 core/grim/src/scene/char_weightable/mod.rs create mode 100644 core/grim/src/scene/character/mod.rs create mode 100644 core/grim/src/scene/object_dir_object/mod.rs create mode 100644 core/grim/src/scene/rnd_dir/mod.rs diff --git a/core/grim/src/scene/band_character/mod.rs b/core/grim/src/scene/band_character/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/grim/src/scene/char_driver/mod.rs b/core/grim/src/scene/char_driver/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/grim/src/scene/char_weightable/mod.rs b/core/grim/src/scene/char_weightable/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/grim/src/scene/character/mod.rs b/core/grim/src/scene/character/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index 0caa24b1..d00fc317 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -1,10 +1,14 @@ mod anim; mod band_placer; +mod band_character; mod cam; mod char_bones_samples; mod char_clip; mod char_clip_samples; +mod char_driver; +mod char_weightable; mod char_lip_sync; +mod character; mod cube_tex; mod draw; mod group; @@ -16,10 +20,12 @@ mod meta; mod milo; mod morph; mod object_dir; +mod object_dir_object; mod object; mod p9_song_pref; mod poll; mod prop_anim; +mod rnd_dir; mod synth_sample; mod tex; mod trans; @@ -27,11 +33,15 @@ mod trans_anim; pub use anim::*; pub use band_placer::*; +pub use band_character::*; pub use cam::*; pub use char_clip::*; pub use char_bones_samples::*; pub use char_clip_samples::*; +pub use char_driver::*; +pub use char_weightable::*; pub use char_lip_sync::*; +pub use character::*; pub use cube_tex::*; pub use draw::*; pub use group::*; @@ -44,10 +54,12 @@ pub use self::mesh_anim::*; pub use self::milo::*; pub use self::morph::*; pub use self::object_dir::*; +pub use self::object_dir_object::*; pub use self::object::*; pub use p9_song_pref::*; pub use poll::*; pub use prop_anim::*; +pub use rnd_dir::*; pub use synth_sample::*; pub use tex::*; pub use trans::*; diff --git a/core/grim/src/scene/object_dir_object/mod.rs b/core/grim/src/scene/object_dir_object/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/grim/src/scene/rnd_dir/mod.rs b/core/grim/src/scene/rnd_dir/mod.rs new file mode 100644 index 00000000..e69de29b From bb320b7c70834cf3b3c22f142bc0fd9b57d6f7ad Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 15 Jul 2023 22:37:15 -0400 Subject: [PATCH 105/113] Update crates --- Cargo.toml | 12 ++++++------ apps/cli/p9_scene_tool/Cargo.toml | 8 ++++---- core/grim/Cargo.toml | 16 ++++++++-------- utils/anim_preview/Cargo.toml | 2 +- utils/lipsync_preview/Cargo.toml | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd04b34e..2d29387a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,15 +18,15 @@ authors = ["PikminGuts92"] edition = "2021" [workspace.dependencies] -clap = { version = "4.2.1", features = ["derive"] } +clap = { version = "4.3.12", features = ["derive"] } grim = { path = "core/grim" } -itertools = "0.10.5" +itertools = "0.11.0" lazy_static = "1.4.0" -log = "0.4.17" -serde = { version = "1.0.160", features = ["derive"] } -serde_json = "1.0.95" +log = "0.4.19" +serde = { version = "1.0.171", features = ["derive"] } +serde_json = "1.0.103" simplelog = "0.12.1" -thiserror = "1.0.40" +thiserror = "1.0.43" [profile.dev.package."*"] opt-level = 3 diff --git a/apps/cli/p9_scene_tool/Cargo.toml b/apps/cli/p9_scene_tool/Cargo.toml index 5bac5a9d..666a634b 100644 --- a/apps/cli/p9_scene_tool/Cargo.toml +++ b/apps/cli/p9_scene_tool/Cargo.toml @@ -5,9 +5,9 @@ authors.workspace = true edition.workspace = true [dependencies] -build-time = "0.1.2" +build-time = "0.1.3" clap = { workspace = true } -const_format = "0.2.30" +const_format = "0.2.31" grim = { workspace = true, features = [ "midi" ] } log = { workspace = true } serde = { workspace = true } @@ -16,5 +16,5 @@ simplelog = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -criterion = "0.4.0" -rstest = "0.17.0" +criterion = "0.5.1" +rstest = "0.18.1" diff --git a/core/grim/Cargo.toml b/core/grim/Cargo.toml index 1d3a8147..85257f06 100644 --- a/core/grim/Cargo.toml +++ b/core/grim/Cargo.toml @@ -5,34 +5,34 @@ authors.workspace = true edition.workspace = true [dependencies] -base64 = "0.21.0" -bitstream-io = { version = "1.6.0", optional = true } +base64 = "0.21.2" +bitstream-io = { version = "1.7.0", optional = true } ffmpeg-next = { version = "6.0.0", optional = true } -flate2 = "1.0.25" +flate2 = "1.0.26" fon = { version = "0.6.0", optional = true } gltf = { version = "1.2.0", optional = true, features = [ "extras", "import", "names" ] } gltf-json = { version = "1.2.0", optional = true, features = [ "names" ] } grim_macros = { path = "../grim_macros" } grim_traits = { path = "../grim_traits" } -half = { version = "2.2.1", default-features = false } +half = { version = "2.3.1", default-features = false } image = { version = "0.24.6", default-features = false, features = [ "dxt", "jpeg", "png" ] } itertools = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } midly = { version = "0.5.3", optional = true } -nalgebra = { version = "0.32.2", optional = true } +nalgebra = { version = "0.32.3", optional = true } nom = "7.1.3" # pyo3 = { version = "0.17.3", optional = true, features = [ "extension-module" ] } pyo3 = { git = "https://github.com/PyO3/pyo3", branch = "cfg-feature-pyo3", optional = true, features = [ "extension-module" ] } rayon = "1.7.0" -regex = { version = "1.7.3", default-features = false, features = [ "std" ] } +regex = { version = "1.9.1", default-features = false, features = [ "std" ] } serde = { optional = true, workspace = true } thiserror = { workspace = true } wav = { version = "1.0.0", optional = true } [dev-dependencies] -criterion = "0.4.0" -rstest = "0.17.0" +criterion = "0.5.1" +rstest = "0.18.1" [features] audio = [ "bitstream-io", "fon", "wav" ] diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index f6c6def8..616ba823 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -7,6 +7,6 @@ edition.workspace = true [dependencies] grim = { workspace = true } keyframe = "1.1.1" -nalgebra = "0.32.2" +nalgebra = "0.32.3" rerun = "0.4.0" shared = { path = "../shared" } \ No newline at end of file diff --git a/utils/lipsync_preview/Cargo.toml b/utils/lipsync_preview/Cargo.toml index 9c890089..8fb63b7d 100644 --- a/utils/lipsync_preview/Cargo.toml +++ b/utils/lipsync_preview/Cargo.toml @@ -8,6 +8,6 @@ edition.workspace = true grim = { workspace = true, features = [ "audio" ] } eframe = "0.21.3" keyframe = "1.1.1" -nalgebra = "0.32.2" +nalgebra = "0.32.3" #rerun = "0.2.0" shared = { path = "../shared" } \ No newline at end of file From 0176d832c580a6ad379e6378b9ec6af277ee57e5 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 15 Jul 2023 22:53:59 -0400 Subject: [PATCH 106/113] Update bevy to 0.11 --- apps/ui/preview_ui/Cargo.toml | 12 +++++++----- apps/ui/preview_ui/src/events.rs | 3 +++ apps/ui/preview_ui/src/main.rs | 20 ++++++++++---------- apps/ui/preview_ui/src/render/milo_entry.rs | 3 +-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/apps/ui/preview_ui/Cargo.toml b/apps/ui/preview_ui/Cargo.toml index 9a0cac94..f24ea0bf 100644 --- a/apps/ui/preview_ui/Cargo.toml +++ b/apps/ui/preview_ui/Cargo.toml @@ -5,11 +5,13 @@ authors.workspace = true edition.workspace = true [dependencies] -bevy = { version = "0.10.1", default-features = false, features = [ "bevy_core_pipeline", "bevy_pbr", "bevy_render", "bevy_winit", "x11" ] } -bevy_egui = { version = "0.20.2", features = [ "immutable_ctx" ] } -bevy_fly_camera = "0.10.0" -bevy_infinite_grid = "0.7.0" -egui_extras = { version = "0.21.0", features = [ "svg" ] } +bevy = { version = "0.11.0", default-features = false, features = [ "bevy_core_pipeline", "bevy_pbr", "bevy_render", "bevy_winit", "x11" ] } +bevy_egui = { version = "0.21.0", features = [ "immutable_ctx" ] } +#bevy_fly_camera = "0.10.0" +bevy_fly_camera = { git = "https://github.com/zachbateman/bevy_fly_camera.git", branch = "bevy_0.11.0" } +#bevy_infinite_grid = "0.7.0" +bevy_infinite_grid = { git = "https://github.com/PikminGuts92/bevy_infinite_grid.git", branch = "bevy-0.11" } +egui_extras = { version = "0.22.0", features = [ "svg" ] } font-awesome-as-a-crate = "0.3.0" grim = { workspace = true } itertools = { workspace = true } diff --git a/apps/ui/preview_ui/src/events.rs b/apps/ui/preview_ui/src/events.rs index 9ce4b866..f31a2958 100644 --- a/apps/ui/preview_ui/src/events.rs +++ b/apps/ui/preview_ui/src/events.rs @@ -1,5 +1,7 @@ +use bevy::prelude::*; use std::path::PathBuf; +#[derive(Event)] pub enum AppEvent { Exit, SelectMiloEntry(Option), @@ -7,6 +9,7 @@ pub enum AppEvent { ToggleWireframes(bool), } +#[derive(Event)] pub enum AppFileEvent { Open(PathBuf), } \ No newline at end of file diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs index 7fa43d5e..747d7754 100644 --- a/apps/ui/preview_ui/src/main.rs +++ b/apps/ui/preview_ui/src/main.rs @@ -46,15 +46,15 @@ fn main() { .add_plugin(EguiPlugin) .add_plugin(FlyCameraPlugin) .add_plugin(InfiniteGridPlugin) - .add_system(render_gui_system) - .add_system(detect_meshes) - .add_system(control_camera) - .add_system(drop_files) - .add_system(window_resized) - .add_system(consume_file_events) - .add_system(consume_app_events) - .add_startup_system(setup_args) - .add_startup_system(setup) + .add_systems(Update, render_gui_system) + .add_systems(Update, detect_meshes) + .add_systems(Update, control_camera) + .add_systems(Update, drop_files) + .add_systems(Update, window_resized) + .add_systems(Update, consume_file_events) + .add_systems(Update, consume_app_events) + .add_systems(Startup, setup_args) + .add_systems(Startup, setup) .run(); } @@ -445,7 +445,7 @@ fn is_camera_button_down(key_input: &Res>) -> bool { KeyCode::S, KeyCode::D, KeyCode::Space, - KeyCode::LShift, + KeyCode::ShiftLeft, ]; control_keys diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs index 3d973d4a..bc969b65 100644 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ b/apps/ui/preview_ui/src/render/milo_entry.rs @@ -9,7 +9,6 @@ use std::collections::HashMap; use std::error::Error; use std::fs; use std::io::Read; -use std::num::NonZeroU8; use std::path::{Path, PathBuf}; use thiserror::Error; @@ -520,7 +519,7 @@ fn map_texture<'a>(tex: &'a (&'a Tex, Vec, ImageInfo)) -> Image { texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor { address_mode_u: AddressMode::Repeat, address_mode_v: AddressMode::Repeat, - anisotropy_clamp: NonZeroU8::new(16), + anisotropy_clamp: 1, // 16 ..SamplerDescriptor::default() }); From 5875b6212e36d18a6dfeb125ff5eed1aea9e6e95 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sat, 15 Jul 2023 23:10:48 -0400 Subject: [PATCH 107/113] Fix vert/face counter --- apps/ui/preview_ui/src/main.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs index 747d7754..c4394607 100644 --- a/apps/ui/preview_ui/src/main.rs +++ b/apps/ui/preview_ui/src/main.rs @@ -42,10 +42,10 @@ fn main() { .add_event::() //.insert_resource(ClearColor(Color::BLACK)) .insert_resource(Msaa::Sample4) - .add_plugin(GrimPlugin) - .add_plugin(EguiPlugin) - .add_plugin(FlyCameraPlugin) - .add_plugin(InfiniteGridPlugin) + .add_plugins(GrimPlugin) + .add_plugins(EguiPlugin) + .add_plugins(FlyCameraPlugin) + .add_plugins(InfiniteGridPlugin) .add_systems(Update, render_gui_system) .add_systems(Update, detect_meshes) .add_systems(Update, control_camera) @@ -71,28 +71,14 @@ fn render_gui_system(mut settings: ResMut, mut state: ResMut, - meshes: Res>, - mesh_entities: Query<(&Handle, &WorldMesh, Option<&Visibility>)>, - //added_meshes: Query<(&WorldMesh, &Handle), Added>, - //removed_meshes: RemovedComponents, + mesh_entities: Query<&WorldMesh>, ) { let mut vertex_count = 0; let mut face_count = 0; - for (mesh_id, world_mesh, visibility) in mesh_entities.iter() { - if let Some(_mesh) = meshes.get(mesh_id) { - let is_visible = visibility - .map_or(false, |v| v == Visibility::Visible); - - // Ignore invisible meshes - if !is_visible { - continue; - } - - //vertex_count += mesh.count_vertices(); - vertex_count += world_mesh.vert_count; - face_count += world_mesh.face_count; - } + for world_mesh in mesh_entities.iter() { + vertex_count += world_mesh.vert_count; + face_count += world_mesh.face_count; } // Update counts From 29843ce71baa76e411563d6a83d3db15ee49b840 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 16 Jul 2023 00:25:07 -0400 Subject: [PATCH 108/113] Update rerun --- utils/anim_preview/Cargo.toml | 2 +- utils/anim_preview/src/main.rs | 62 +++++++++++++++++--------------- utils/lipsync_preview/Cargo.toml | 2 +- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index 616ba823..d4451a90 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -8,5 +8,5 @@ edition.workspace = true grim = { workspace = true } keyframe = "1.1.1" nalgebra = "0.32.3" -rerun = "0.4.0" +rerun = { version = "0.7.0", features = [ "native_viewer" ] } shared = { path = "../shared" } \ No newline at end of file diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index eba8e32f..665b3db2 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -17,9 +17,10 @@ use nalgebra as na; use rerun::external::glam; use rerun::{ coordinates::{Handedness, SignedAxis3}, - components::{Arrow3D, ColorRGBA, LineStrip3D, MeshId, Point3D, Quaternion, Radius, RawMesh3D, Rigid3, Scalar, TextEntry, Transform, Vec3D, ViewCoordinates}, - MsgSender, Session, SessionBuilder, - time::Timeline + components::{Arrow3D, ColorRGBA, LineStrip3D, MeshId, Point3D, Quaternion, Radius, RawMesh3D, Scalar, TextEntry, Transform3D, Vec3D, ViewCoordinates}, + MsgSender, RecordingStream, RecordingStreamBuilder, + time::Timeline, + transform::{Transform3DRepr, TranslationRotationScale3D}, }; use shared::*; @@ -78,7 +79,7 @@ fn main() -> Result<(), Box> { }) .collect::>(); - let mut session = SessionBuilder::new("anim_preview").buffered(); + let (mut rec_stream, storage) = RecordingStreamBuilder::new("anim_preview").memory()?; MsgSender::new(format!("world")) /*.with_component(&[ @@ -91,7 +92,7 @@ fn main() -> Result<(), Box> { SignedAxis3::POSITIVE_Z, Handedness::Right)) .unwrap() - /*.with_splat(Transform::Rigid3({ + /*.with_splat(Transform3D::Rigid3({ let q = na::UnitQuaternion ::from_axis_angle( &na::Vector3::z_axis(), @@ -105,7 +106,7 @@ fn main() -> Result<(), Box> { })) .unwrap()*/ .with_timeless(true) - .send(&mut session) + .send(&mut rec_stream) .unwrap(); for mesh_anim in mesh_anims { @@ -162,14 +163,14 @@ fn main() -> Result<(), Box> { MsgSender::new(mesh_anim.get_name().as_str()) .with_component(&glam_points)? .with_time(Timeline::new_sequence("frame"), i as i64) - .send(&mut session) + .send(&mut rec_stream) .unwrap(); // Send line strip to rerun /*MsgSender::new(mesh_anim.get_name().as_str()) .with_component(&[strip])? .with_time(Timeline::new_sequence("frame"), i as i64) - .send(&mut session) + .send(&rec_stream) .unwrap();*/ } } @@ -248,31 +249,31 @@ fn main() -> Result<(), Box> { // TODO: Iterpolate from frames root_bone.recompute_world_anim_transform(na::Matrix4::identity(), &bone_sample_map, i); - add_bones_to_session(&root_bone, &mut session, i); + add_bones_to_stream(&root_bone, &rec_stream, i); } /*for (char_bone_sample, frames) in bone_samples { //char_bone_sample. }*/ } else { - add_bones_to_session(&root_bone, &mut session, 0); + add_bones_to_stream(&root_bone, &rec_stream, 0); // Can probably delete /*let (points, lines) = generate_bone_points(&root_bone); MsgSender::new(root_bone.name) .with_component(&points)? - .send(&mut session) + .send(&rec_stream) .unwrap(); MsgSender::new(format!("{}_lines", root_bone.name)) .with_component(&lines)? - .send(&mut session) + .send(&rec_stream) .unwrap();*/ } } - rerun::native_viewer::show(&session).unwrap(); + rerun::native_viewer::show(storage.take()).unwrap(); Ok(()) } @@ -478,7 +479,7 @@ impl<'a> BoneNode<'a> { } } -fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { +fn add_bones_to_stream(bone: &BoneNode, rec_stream: &RecordingStream, i: usize) { let v = bone.get_world_anim_pos(); // Generate line strips @@ -497,18 +498,21 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { Point3D::from([v[0], v[1], v[2]]) ]) .unwrap() - .with_splat(Transform::Rigid3({ - let q = na::UnitQuaternion - ::from_axis_angle( - &na::Vector3::z_axis(), - std::f32::consts::PI - ); + .with_splat(Transform3D { + transform: Transform3DRepr::TranslationRotationScale({ + let q = na::UnitQuaternion + ::from_axis_angle( + &na::Vector3::z_axis(), + std::f32::consts::PI + ); - Rigid3 { - rotation: Quaternion::new(q.i, q.j, q.k, q.w), - ..Default::default() - } - })) + TranslationRotationScale3D { + rotation: Some(Quaternion::new(q.i, q.j, q.k, q.w).into()), + ..Default::default() + } + }), + from_parent: true + }) .unwrap() /*.with_splat(ViewCoordinates::from_up_and_handedness( SignedAxis3::POSITIVE_Z, @@ -517,7 +521,7 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { //.with_splat(Radius(1.0)) //.unwrap() .with_time(Timeline::new_sequence("frame"), i as i64) - .send(session) + .send(rec_stream) .unwrap(); // Add lines from node to children @@ -529,7 +533,7 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { Handedness::Right)) .unwrap()*/ .with_time(Timeline::new_sequence("frame"), i as i64) - .send(session) + .send(rec_stream) .unwrap(); // Add direction arrow (not working) @@ -562,11 +566,11 @@ fn add_bones_to_session(bone: &BoneNode, session: &mut Session, i: usize) { Handedness::Right)) .unwrap()*/ .with_time(Timeline::new_sequence("frame"), i as i64) - .send(session) + .send(rec_stream) .unwrap(); for ch in bone.children.iter() { - add_bones_to_session(ch, session, i); + add_bones_to_stream(ch, rec_stream, i); } } diff --git a/utils/lipsync_preview/Cargo.toml b/utils/lipsync_preview/Cargo.toml index 8fb63b7d..c697f569 100644 --- a/utils/lipsync_preview/Cargo.toml +++ b/utils/lipsync_preview/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true [dependencies] grim = { workspace = true, features = [ "audio" ] } -eframe = "0.21.3" +eframe = "0.22.0" keyframe = "1.1.1" nalgebra = "0.32.3" #rerun = "0.2.0" From 0314329a0b11516cc5ddb28473b720032d92a61e Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Wed, 19 Jul 2023 22:40:03 -0400 Subject: [PATCH 109/113] Support loading gh2 char hair --- core/grim/src/scene/char_hair/io.rs | 106 ++++++++++++++++++++++++++ core/grim/src/scene/char_hair/mod.rs | 107 +++++++++++++++++++++++++++ core/grim/src/scene/mod.rs | 2 + core/grim/src/scene/object.rs | 4 + 4 files changed, 219 insertions(+) create mode 100644 core/grim/src/scene/char_hair/io.rs create mode 100644 core/grim/src/scene/char_hair/mod.rs diff --git a/core/grim/src/scene/char_hair/io.rs b/core/grim/src/scene/char_hair/io.rs new file mode 100644 index 00000000..3a0f3124 --- /dev/null +++ b/core/grim/src/scene/char_hair/io.rs @@ -0,0 +1,106 @@ +use crate::io::{BinaryStream, SeekFrom, Stream}; +use crate::scene::*; +use crate::SystemInfo; +use grim_traits::scene::*; +use std::error::Error; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum CharHairLoadError { + #[error("CharHair version {version} is not supported")] + CharHairVersionNotSupported { + version: u32 + }, +} + +fn is_version_supported(version: u32) -> bool { + match version { + 2 => true, // GH2/GH2 360 + //11 => true, // TBRB + _ => false + } +} + +impl ObjectReadWrite for CharHair { + fn load(&mut self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut reader = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + let version = reader.read_uint32()?; + if !is_version_supported(version) { + return Err(Box::new(CharHairLoadError::CharHairVersionNotSupported { + version + })); + } + + load_object(self, &mut reader, info)?; + + self.stiffness = reader.read_float32()?; + self.torsion = reader.read_float32()?; + self.inertia = reader.read_float32()?; + self.gravity = reader.read_float32()?; + + self.weight = reader.read_float32()?; + self.friction = reader.read_float32()?; + + if version >= 11 { + self.min_slack = reader.read_float32()?; + self.max_slack = reader.read_float32()?; + } + + // Read strands + let strand_count = reader.read_uint32()?; + for _ in 0..strand_count { + let mut strand = CharHairStrand::default(); + + strand.root = reader.read_prefixed_string()?; + strand.angle = reader.read_float32()?; + + // Read points + let point_count = reader.read_uint32()?; + for _ in 0..point_count { + let mut point = CharHairPoint::default(); + + load_vector3(&mut point.unknown_floats, &mut reader)?; + point.bone = reader.read_prefixed_string()?; + + point.length = reader.read_float32()?; + point.collide_type = reader.read_uint32()?.into(); + point.collision = reader.read_prefixed_string()?; + + point.distance = reader.read_float32()?; + point.align_dist = reader.read_float32()?; + + strand.points.push(point); + } + + // Read unknown floats + for f in strand.unknown_floats.iter_mut() { + *f = reader.read_float32()?; + } + + self.strands.push(strand); + } + + self.simulate = reader.read_boolean()?; + + if version >= 11 { + self.wind = reader.read_prefixed_string()?; + } + + Ok(()) + } + + fn save(&self, stream: &mut dyn Stream, info: &SystemInfo) -> Result<(), Box> { + let mut stream = Box::new(BinaryStream::from_stream_with_endian(stream, info.endian)); + + // TODO: Get version from system info + let version = 2; + + stream.write_uint32(version)?; + + save_object(self, &mut stream, info)?; + + todo!("Support for writing CharHair not implemented yet"); + //Ok(()) + } +} \ No newline at end of file diff --git a/core/grim/src/scene/char_hair/mod.rs b/core/grim/src/scene/char_hair/mod.rs new file mode 100644 index 00000000..5db71f3b --- /dev/null +++ b/core/grim/src/scene/char_hair/mod.rs @@ -0,0 +1,107 @@ +mod io; + +use grim_macros::*; +use grim_traits::scene::*; +pub use io::*; + +#[milo] +pub struct CharHair { + pub stiffness: f32, + pub torsion: f32, + pub inertia: f32, + pub gravity: f32, + + pub weight: f32, + pub friction: f32, + + pub min_slack: f32, + pub max_slack: f32, + + pub strands: Vec, + + pub simulate: bool, + pub wind: String, +} + +#[derive(Default)] +pub struct CharHairStrand { + pub root: String, + pub angle: f32, + pub points: Vec, + pub unknown_floats: [f32; 18], +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +#[repr(u32)] +pub enum CollideType { + kCollidePlane, + kCollideSphere, + kCollideInsideSphere, + kCollideCylinder, + kCollideInsideCylinder +} + +impl Default for CollideType { + fn default() -> Self { + CollideType::kCollideCylinder + } +} + +impl From for CollideType { + fn from(num: u32) -> CollideType { + match num { + 0 => CollideType::kCollidePlane, + 1 => CollideType::kCollideSphere, + 2 => CollideType::kCollideInsideSphere, + 3 => CollideType::kCollideCylinder, + 4 => CollideType::kCollideInsideCylinder, + // Default + _ => CollideType::default(), + } + } +} + +// TODO: Implement impl From for usize + +#[derive(Default)] +pub struct CharHairPoint { + pub unknown_floats: Vector3, + pub bone: String, + + pub length: f32, + pub collide_type: CollideType, + pub collision: String, + + pub distance: f32, + pub align_dist: f32, +} + +impl Default for CharHair { + fn default() -> CharHair { + // TODO: Match default values to C++ code + CharHair { + // Base object + name: String::default(), + type2: String::default(), + note: String::default(), + + // CharHair object + stiffness: 0.04, + torsion: 0.1, + inertia: 0.6, + gravity: 1.0, + + weight: 1.0, + friction: 0.3, + + min_slack: 0.0, + max_slack: 0.0, + + strands: Vec::new(), + + simulate: true, + wind: String::default(), + } + } +} \ No newline at end of file diff --git a/core/grim/src/scene/mod.rs b/core/grim/src/scene/mod.rs index ad80fb14..bee71889 100644 --- a/core/grim/src/scene/mod.rs +++ b/core/grim/src/scene/mod.rs @@ -6,6 +6,7 @@ mod char_bones_samples; mod char_clip; mod char_clip_samples; mod char_driver; +mod char_hair; mod char_weightable; mod char_lip_sync; mod character; @@ -40,6 +41,7 @@ pub use char_clip::*; pub use char_bones_samples::*; pub use char_clip_samples::*; pub use char_driver::*; +pub use char_hair::*; pub use char_weightable::*; pub use char_lip_sync::*; pub use character::*; diff --git a/core/grim/src/scene/object.rs b/core/grim/src/scene/object.rs index c69986b6..d3af283a 100644 --- a/core/grim/src/scene/object.rs +++ b/core/grim/src/scene/object.rs @@ -7,6 +7,7 @@ pub enum Object { BandPlacer(BandPlacer), Cam(CamObject), CharClipSamples(CharClipSamples), + CharHair(CharHair), CharLipSync(CharLipSync), ColorPalette(ColorPalette), CubeTex(CubeTexObject), @@ -39,6 +40,7 @@ impl Object { Object::BandPlacer(band_placer) => &band_placer.name, Object::Cam(cam) => &cam.name, Object::CharClipSamples(ccs) => &ccs.name, + Object::CharHair(ch) => &ch.name, Object::CharLipSync(cls) => &cls.name, Object::ColorPalette(color_palette) => &color_palette.name, Object::CubeTex(cube) => &cube.name, @@ -64,6 +66,7 @@ impl Object { Object::BandPlacer(_) => "BandPlacer", Object::Cam(_) => "Cam", Object::CharClipSamples(_) => "CharClipSamples", + Object::CharHair(_) => "CharHair", Object::CharLipSync(_) => "CharLipSync", Object::ColorPalette(_) => "ColorPalette", Object::CubeTex(_) => "CubeTex", @@ -138,6 +141,7 @@ impl Object { "BandPlacer" => unpack_object(packed, info).map(|o| Object::BandPlacer(o)), "Cam" => unpack_object(packed, info).map(|o| Object::Cam(o)), "CharClipSamples" => unpack_object(packed, info).map(|o| Object::CharClipSamples(o)), + "CharHair" => unpack_object(packed, info).map(|o| Object::CharHair(o)), "CharLipSync" => unpack_object(packed, info).map(|o| Object::CharLipSync(o)), "ColorPalette" => unpack_object(packed, info).map(|o| Object::ColorPalette(o)), "CubeTex" => unpack_object(packed, info).map(|o| Object::CubeTex(o)), From f180861382a31112ca0a2d7eafdc0947059f6e6a Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 20 Jul 2023 19:43:29 -0400 Subject: [PATCH 110/113] Fix identity typo --- apps/cli/p9_scene_tool/src/apps/project2milo.rs | 2 +- core/grim/src/model/export.rs | 2 +- core/grim/src/scene/mesh/io.rs | 2 +- core/grim_traits/src/scene/common.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/cli/p9_scene_tool/src/apps/project2milo.rs b/apps/cli/p9_scene_tool/src/apps/project2milo.rs index 421e9a30..3d67b5c2 100644 --- a/apps/cli/p9_scene_tool/src/apps/project2milo.rs +++ b/apps/cli/p9_scene_tool/src/apps/project2milo.rs @@ -641,7 +641,7 @@ fn create_object_dir_entry(name: &str, obj_type: &str, subdir_paths: &[&str], in // Viewports const VIEWPORT_COUNT: i32 = 7; - let mat = Matrix::indentity(); + let mat = Matrix::identity(); writer.write_int32(VIEWPORT_COUNT)?; for _ in 0..VIEWPORT_COUNT { diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index 41d98b56..a59806b8 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -127,7 +127,7 @@ fn populate_child_nodes(nodes: &mut Vec, bones: &Vec) let child_indices = populate_child_nodes(nodes, &bone.children); let m = bone.object.get_local_xfm(); - //let m = Matrix::indentity(); + //let m = Matrix::identity(); let mat = na::Matrix4::new( // Column-major order... diff --git a/core/grim/src/scene/mesh/io.rs b/core/grim/src/scene/mesh/io.rs index b80da1cb..4562c5d6 100644 --- a/core/grim/src/scene/mesh/io.rs +++ b/core/grim/src/scene/mesh/io.rs @@ -594,7 +594,7 @@ impl ObjectReadWrite for MeshObject { } else { // Write empty bone stream.write_uint32(0)?; - save_matrix(&Matrix::indentity(), &mut stream)?; + save_matrix(&Matrix::identity(), &mut stream)?; } } diff --git a/core/grim_traits/src/scene/common.rs b/core/grim_traits/src/scene/common.rs index b4566546..94836435 100644 --- a/core/grim_traits/src/scene/common.rs +++ b/core/grim_traits/src/scene/common.rs @@ -112,7 +112,7 @@ pub struct Matrix { } impl Matrix { - pub fn indentity() -> Matrix { + pub fn identity() -> Matrix { Matrix { m11: 1.0, m12: 0.0, @@ -137,6 +137,6 @@ impl Matrix { impl Default for Matrix { fn default() -> Matrix { - Matrix::indentity() + Matrix::identity() } } \ No newline at end of file From 4c4704bb19207680df55bcb330d8efa5d694da90 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Thu, 20 Jul 2023 19:49:58 -0400 Subject: [PATCH 111/113] Use matrix3 for char hair --- core/grim/src/scene/char_hair/io.rs | 7 +++--- core/grim/src/scene/char_hair/mod.rs | 3 ++- core/grim/src/scene/io.rs | 34 +++++++++++++++++++++++++- core/grim_traits/src/scene/common.rs | 36 +++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/core/grim/src/scene/char_hair/io.rs b/core/grim/src/scene/char_hair/io.rs index 3a0f3124..3f501c6a 100644 --- a/core/grim/src/scene/char_hair/io.rs +++ b/core/grim/src/scene/char_hair/io.rs @@ -73,10 +73,9 @@ impl ObjectReadWrite for CharHair { strand.points.push(point); } - // Read unknown floats - for f in strand.unknown_floats.iter_mut() { - *f = reader.read_float32()?; - } + // Read rotation + scale matrices + load_matrix3(&mut strand.base_mat, &mut reader)?; + load_matrix3(&mut strand.root_mat, &mut reader)?; self.strands.push(strand); } diff --git a/core/grim/src/scene/char_hair/mod.rs b/core/grim/src/scene/char_hair/mod.rs index 5db71f3b..f3b5e2bf 100644 --- a/core/grim/src/scene/char_hair/mod.rs +++ b/core/grim/src/scene/char_hair/mod.rs @@ -28,7 +28,8 @@ pub struct CharHairStrand { pub root: String, pub angle: f32, pub points: Vec, - pub unknown_floats: [f32; 18], + pub base_mat: Matrix3, // Usually the same + pub root_mat: Matrix3, } #[allow(non_camel_case_types)] diff --git a/core/grim/src/scene/io.rs b/core/grim/src/scene/io.rs index 5ba5517f..373a0735 100644 --- a/core/grim/src/scene/io.rs +++ b/core/grim/src/scene/io.rs @@ -1,7 +1,7 @@ use crate::dta::*; use crate::io::{BinaryStream, FileStream, SeekFrom, Stream}; use crate::SystemInfo; -use grim_traits::scene::{Color3, Color4, Matrix, MiloObject, Quat, Rect, Sphere, Vector2, Vector3}; +use grim_traits::scene::{Color3, Color4, Matrix, Matrix3, MiloObject, Quat, Rect, Sphere, Vector2, Vector3}; use std::error::Error; use std::path::Path; @@ -161,6 +161,38 @@ pub (crate) fn save_matrix(mat: &Matrix, writer: &mut Box) -> Resu Ok(()) } +pub (crate) fn load_matrix3(mat: &mut Matrix3, reader: &mut Box) -> Result<(), Box> { + mat.m11 = reader.read_float32()?; + mat.m12 = reader.read_float32()?; + mat.m13 = reader.read_float32()?; + + mat.m21 = reader.read_float32()?; + mat.m22 = reader.read_float32()?; + mat.m23 = reader.read_float32()?; + + mat.m31 = reader.read_float32()?; + mat.m32 = reader.read_float32()?; + mat.m33 = reader.read_float32()?; + + Ok(()) +} + +pub (crate) fn save_matrix3(mat: &Matrix3, writer: &mut Box) -> Result<(), Box> { + writer.write_float32(mat.m11)?; + writer.write_float32(mat.m12)?; + writer.write_float32(mat.m13)?; + + writer.write_float32(mat.m21)?; + writer.write_float32(mat.m22)?; + writer.write_float32(mat.m23)?; + + writer.write_float32(mat.m31)?; + writer.write_float32(mat.m32)?; + writer.write_float32(mat.m33)?; + + Ok(()) +} + pub (crate) fn load_rect(rect: &mut Rect, reader: &mut Box) -> Result<(), Box> { rect.x = reader.read_float32()?; rect.y = reader.read_float32()?; diff --git a/core/grim_traits/src/scene/common.rs b/core/grim_traits/src/scene/common.rs index 94836435..b8fa5af3 100644 --- a/core/grim_traits/src/scene/common.rs +++ b/core/grim_traits/src/scene/common.rs @@ -134,9 +134,43 @@ impl Matrix { } } - impl Default for Matrix { fn default() -> Matrix { Matrix::identity() } +} + +#[derive(Debug)] +pub struct Matrix3 { + pub m11: f32, + pub m12: f32, + pub m13: f32, + pub m21: f32, + pub m22: f32, + pub m23: f32, + pub m31: f32, + pub m32: f32, + pub m33: f32, +} + +impl Matrix3 { + pub fn identity() -> Matrix3 { + Matrix3 { + m11: 1.0, + m12: 0.0, + m13: 0.0, + m21: 0.0, + m22: 1.0, + m23: 0.0, + m31: 0.0, + m32: 0.0, + m33: 1.0, + } + } +} + +impl Default for Matrix3 { + fn default() -> Matrix3 { + Matrix3::identity() + } } \ No newline at end of file From 1d0357fea181bc00b7d1461cc7743fb41d7be69c Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Tue, 1 Aug 2023 22:32:29 -0400 Subject: [PATCH 112/113] Support rb3 milo directory --- core/grim/src/io/archive.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/grim/src/io/archive.rs b/core/grim/src/io/archive.rs index bc6a4513..1ac22594 100644 --- a/core/grim/src/io/archive.rs +++ b/core/grim/src/io/archive.rs @@ -175,6 +175,10 @@ impl MiloArchive { reader.seek(SeekFrom::Current(8))?; // Skip extra nums + if version >= 32 { + reader.seek(SeekFrom::Current(1))?; // Skip unknown bool + } + // Update class name ObjectDir::fix_class_name(version, &mut dir_type); } else { From 8e076c3f5cabfc490f8b9ee457b3e14af2d27238 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Mon, 14 Aug 2023 21:49:58 -0400 Subject: [PATCH 113/113] Update bevy_infinite_grid --- apps/ui/preview_ui/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/ui/preview_ui/Cargo.toml b/apps/ui/preview_ui/Cargo.toml index f24ea0bf..f0916c4f 100644 --- a/apps/ui/preview_ui/Cargo.toml +++ b/apps/ui/preview_ui/Cargo.toml @@ -9,8 +9,7 @@ bevy = { version = "0.11.0", default-features = false, features = [ "bevy_core_p bevy_egui = { version = "0.21.0", features = [ "immutable_ctx" ] } #bevy_fly_camera = "0.10.0" bevy_fly_camera = { git = "https://github.com/zachbateman/bevy_fly_camera.git", branch = "bevy_0.11.0" } -#bevy_infinite_grid = "0.7.0" -bevy_infinite_grid = { git = "https://github.com/PikminGuts92/bevy_infinite_grid.git", branch = "bevy-0.11" } +bevy_infinite_grid = "0.8.0" egui_extras = { version = "0.22.0", features = [ "svg" ] } font-awesome-as-a-crate = "0.3.0" grim = { workspace = true }