#![feature(array_chunks)] use anyhow::{Result, anyhow}; use glam::{Affine3A, Mat4}; use gltf::{Glb, Semantic, mesh::Mode}; use gltf_json::{ Accessor, Index, Node, Root, Scene, accessor::GenericComponentType, mesh::Primitive, validation::{Checked, USize64}, }; use log::warn; use std::{borrow::Cow, collections::BTreeMap, env::args, fs::File, io::BufReader}; use unity_tools::{ classes::{ gameobject::GameObject, mesh::{Mesh, VertexDataChannel}, renderer::SkinnedMeshRenderer, transform::Transform, }, serialized_file::SerializedFile, unityfs::UnityFS, }; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); let file = BufReader::new(File::open(args().nth(1).unwrap()).unwrap()); let fs = UnityFS::open(file)?; let cabfile = fs .find_main_file() .ok_or(anyhow!("no CAB file found"))? .to_owned(); let mut cab = fs.read(&cabfile)?; let mut file = SerializedFile::read(&mut cab)?; let gameobjects = file .objects .iter() .filter(|ob| file.get_object_type_tree(ob).unwrap().type_string == "GameObject") .cloned() .collect::>(); // let mut root_tr = Transform::from_value(file.read_object(ob)?)?; // while !root_tr.father.is_null() { // root_tr = root_tr.father.load(&mut file)?; // } // eprintln!("{root_tr:?}"); let mut root = gltf_json::Root::default(); let mut nodes = Vec::new(); let mut buffer = Vec::new(); for ob in gameobjects { let go = file.read_object(ob)?.parse::()?; let mut global_transform = Affine3A::default(); for comp in go.components { let ob = comp.load(&mut file, None)?; match ob.class_name().unwrap().as_str() { "Transform" => { let mut tr = ob.parse::()?; let mut transforms = Vec::new(); loop { transforms.push( Mat4::from_translation(tr.local_position) * Mat4::from_scale(tr.local_scale) * Mat4::from_quat(tr.local_rotation), ); if tr.father.is_null() { break; } else { tr = tr.father.load(&mut file, None)?; } } global_transform = Affine3A::from_mat4(transforms.into_iter().reduce(|a, b| a * b).unwrap()) } "SkinnedMeshRenderer" => { let smr = ob.parse::()?; let mesh = import_mesh( &mut root, &mut buffer, smr.mesh_renderer.mesh.load(&mut file, None)?, )?; nodes.push(root.push(Node { mesh: Some(mesh), ..Default::default() })); } "MeshRenderer" => { // let mr = ob.parse::()?; } x => warn!("unknown component {x:?}"), } } } root.push(Scene { extensions: Default::default(), extras: Default::default(), name: None, nodes, }); let json_string = gltf_json::serialize::to_string(&root).expect("Serialization error"); let mut json_offset = json_string.len(); align_to_multiple_of_four(&mut json_offset); let glb = Glb { header: gltf::binary::Header { magic: *b"glTF", version: 2, length: json_offset as u32, }, bin: Some(Cow::Owned(buffer)), json: Cow::Owned(json_string.into_bytes()), }; let writer = std::fs::File::create("/tmp/a.glb").expect("I/O error"); glb.to_writer(writer).expect("glTF binary output error"); Ok(()) } fn align_to_multiple_of_four(n: &mut usize) { *n = (*n + 3) & !3; } pub fn import_mesh( root: &mut Root, buffer: &mut Vec, mesh: Mesh, ) -> Result> { let indicies = mesh.read_indecies(); let (pdim, positions) = mesh .vertex_data .read_channel(VertexDataChannel::Position) .ok_or(anyhow!("mesh has no positions"))?; assert_eq!(pdim, 3); let positions = { let buffer_length = positions.len() * size_of::<[f32; 3]>(); let buffer = root.push(gltf_json::Buffer { byte_length: USize64::from(buffer_length), name: None, uri: None, extensions: Default::default(), extras: Default::default(), }); let buffer_view = root.push(gltf_json::buffer::View { buffer, byte_length: USize64::from(buffer_length), byte_offset: None, byte_stride: Some(gltf_json::buffer::Stride(size_of::<[f32; 3]>())), name: None, target: Some(Checked::Valid(gltf_json::buffer::Target::ArrayBuffer)), extensions: Default::default(), extras: Default::default(), }); root.push(Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64(0)), count: USize64::from(positions.len()), component_type: Checked::Valid(GenericComponentType( gltf_json::accessor::ComponentType::F32, )), extensions: Default::default(), extras: Default::default(), type_: Checked::Valid(gltf_json::accessor::Type::Vec3), max: None, min: None, name: None, normalized: false, sparse: None, }) }; let indices = { let offset = buffer.len(); let num_indices = indicies.len(); buffer.extend(indicies.into_iter().flatten().flat_map(u32::to_le_bytes)); let buffer_length = num_indices * size_of::() * 3; let buffer = root.push(gltf_json::Buffer { byte_length: USize64::from(buffer_length), name: None, uri: None, extensions: Default::default(), extras: Default::default(), }); let buffer_view = root.push(gltf_json::buffer::View { buffer, byte_length: USize64::from(buffer_length), byte_offset: Some(USize64::from(offset)), byte_stride: Some(gltf_json::buffer::Stride(size_of::())), name: None, target: Some(Checked::Valid(gltf_json::buffer::Target::ArrayBuffer)), extensions: Default::default(), extras: Default::default(), }); root.push(Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64(0)), count: USize64::from(num_indices * 3), component_type: Checked::Valid(GenericComponentType( gltf_json::accessor::ComponentType::U32, )), extensions: Default::default(), extras: Default::default(), type_: Checked::Valid(gltf_json::accessor::Type::Vec3), max: None, min: None, name: None, normalized: false, sparse: None, }) }; let primitive = Primitive { attributes: { let mut map = BTreeMap::new(); map.insert(Checked::Valid(Semantic::Positions), positions); map }, extensions: Default::default(), extras: Default::default(), indices: Some(indices), material: None, mode: Checked::Valid(Mode::Triangles), targets: None, }; let mesh = root.push(gltf_json::Mesh { extensions: Default::default(), extras: Default::default(), name: None, primitives: vec![primitive], weights: None, }); Ok(mesh) }