diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-14 17:45:19 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-14 17:45:19 +0100 |
commit | 07fc3656274117c211ca0d6a54926d390a4d9b68 (patch) | |
tree | c97cff08261c475611e61321967cdf272c9ddffd | |
parent | 7ff78cff53eba1da60b8beb851732e2f8197c221 (diff) | |
download | unity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar unity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar.bz2 unity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar.zst |
initial work on gltf exports
-rw-r--r-- | Cargo.lock | 63 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | exporter/Cargo.toml | 8 | ||||
-rw-r--r-- | exporter/src/bin/gltf.rs | 151 | ||||
-rw-r--r-- | src/classes/mesh.rs | 41 |
5 files changed, 238 insertions, 33 deletions
@@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -288,14 +288,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -456,12 +456,6 @@ dependencies = [ ] [[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] name = "image" version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -549,6 +543,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -609,9 +627,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "loop9" @@ -776,6 +794,21 @@ dependencies = [ ] [[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1005,9 +1038,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -9,12 +9,12 @@ version = "0.1.0" edition = "2024" [dependencies] -log = "0.4.25" -env_logger = "0.11.6" -anyhow = "1.0.95" +log = "0.4.26" +env_logger = "0.11.7" +anyhow = "1.0.97" lz4_flex = "0.11.3" lzma = "0.2.2" -serde_json = "1.0.139" +serde_json = "1.0.140" humansize = "2.1.3" serde = { version = "1.0.219", features = ["derive"] } glam = { version = "0.30.0", features = ["serde"] } diff --git a/exporter/Cargo.toml b/exporter/Cargo.toml index 1894e52..02eca5c 100644 --- a/exporter/Cargo.toml +++ b/exporter/Cargo.toml @@ -6,10 +6,10 @@ edition = "2024" [dependencies] unity-tools = { path = ".." } serde_yml = "0.0.12" -log = "0.4.25" -env_logger = "0.11.6" -serde_json = "1.0.139" -anyhow = "1.0.95" +log = "0.4.26" +env_logger = "0.11.7" +serde_json = "1.0.140" +anyhow = "1.0.97" glam = { version = "0.30.0", features = ["serde"] } gltf-json = "1.4.1" gltf = "1.4.1" diff --git a/exporter/src/bin/gltf.rs b/exporter/src/bin/gltf.rs index fb6b72d..86f48b4 100644 --- a/exporter/src/bin/gltf.rs +++ b/exporter/src/bin/gltf.rs @@ -1,13 +1,20 @@ #![feature(array_chunks)] -use anyhow::anyhow; +use anyhow::{Result, anyhow}; use glam::{Affine3A, Mat4}; -use gltf::Glb; +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, env::args, fs::File, io::BufReader}; +use std::{borrow::Cow, collections::BTreeMap, env::args, fs::File, io::BufReader}; use unity_tools::{ classes::{ gameobject::GameObject, - mesh_renderer::{MeshRenderer, SkinnedMeshRenderer}, + mesh::{Mesh, VertexDataChannel}, + mesh_renderer::SkinnedMeshRenderer, transform::Transform, }, serialized_file::SerializedFile, @@ -40,12 +47,14 @@ fn main() -> anyhow::Result<()> { // 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::<GameObject>()?; + let mut global_transform = Affine3A::default(); for comp in go.components { let ob = comp.load(&mut file)?; - let mut global_transform = Affine3A::default(); match ob.class_name().unwrap().as_str() { "Transform" => { let mut tr = ob.parse::<Transform>()?; @@ -66,15 +75,30 @@ fn main() -> anyhow::Result<()> { Affine3A::from_mat4(transforms.into_iter().reduce(|a, b| a * b).unwrap()) } "SkinnedMeshRenderer" => { - let mr = ob.parse::<SkinnedMeshRenderer>()?; + let smr = ob.parse::<SkinnedMeshRenderer>()?; + let mesh = import_mesh( + &mut root, + &mut buffer, + smr.mesh_renderer.mesh.load(&mut file)?, + )?; + nodes.push(root.push(Node { + mesh: Some(mesh), + ..Default::default() + })); } "MeshRenderer" => { - let mr = ob.parse::<MeshRenderer>()?; + // let mr = ob.parse::<MeshRenderer>()?; } 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(); @@ -85,7 +109,7 @@ fn main() -> anyhow::Result<()> { version: 2, length: json_offset as u32, }, - bin: None, + bin: Some(Cow::Owned(buffer)), json: Cow::Owned(json_string.into_bytes()), }; let writer = std::fs::File::create("triangle.glb").expect("I/O error"); @@ -97,3 +121,114 @@ fn main() -> anyhow::Result<()> { fn align_to_multiple_of_four(n: &mut usize) { *n = (*n + 3) & !3; } + +pub fn import_mesh( + root: &mut Root, + buffer: &mut Vec<u8>, + mesh: Mesh, +) -> Result<Index<gltf_json::Mesh>> { + 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::<u32>() * 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::<u32>())), + 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) +} diff --git a/src/classes/mesh.rs b/src/classes/mesh.rs index f0b2173..f828b7c 100644 --- a/src/classes/mesh.rs +++ b/src/classes/mesh.rs @@ -1,7 +1,8 @@ use super::streaminginfo::StreamingInfo; use crate::object::{Value, parser::FromValue}; use anyhow::{Result, anyhow, bail}; -use glam::Mat4; +use glam::{Mat4, Vec3, Vec3A}; +use log::debug; use serde::Serialize; use std::mem::transmute; @@ -154,7 +155,7 @@ impl VertexData { let component_offset = channel.offset as usize; let component_size = channel.format.component_size(); let (offset, stride) = self.stream_layout()[channel.stream as usize]; - + debug!("reading {channel:?} vertex channel (stride={stride}, offset={offset})"); let mut out = Vec::new(); for vi in 0..self.vertex_count as usize { for di in 0..channel.dimension as usize { @@ -178,6 +179,42 @@ impl VertexData { } Some((channel.dimension as usize, out)) } + pub fn read_channel_vec<T: VectorType>( + &self, + channel: VertexDataChannel, + ) -> Result<Option<Vec<T>>> { + let Some((dim, data)) = self.read_channel(channel) else { + return Ok(None); + }; + if dim != T::DIM { + bail!( + "dimension mismatch reading {channel:?} channel ({} != {})", + dim, + T::DIM + ); + } + Ok(Some(VectorType::convert_array(data))) + } +} + +pub trait VectorType: Sized { + const DIM: usize; + fn convert_array(a: Vec<f32>) -> Vec<Self>; +} +impl VectorType for Vec3A { + const DIM: usize = 3; + fn convert_array(a: Vec<f32>) -> Vec<Self> { + a.into_iter() + .array_chunks() + .map(Vec3A::from_array) + .collect() + } +} +impl VectorType for Vec3 { + const DIM: usize = 3; + fn convert_array(a: Vec<f32>) -> Vec<Self> { + a.into_iter().array_chunks().map(Vec3::from_array).collect() + } } impl FromValue for ChannelInfo { |