aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-14 17:45:19 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-14 17:45:19 +0100
commit07fc3656274117c211ca0d6a54926d390a4d9b68 (patch)
treec97cff08261c475611e61321967cdf272c9ddffd
parent7ff78cff53eba1da60b8beb851732e2f8197c221 (diff)
downloadunity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar
unity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar.bz2
unity-tools-07fc3656274117c211ca0d6a54926d390a4d9b68.tar.zst
initial work on gltf exports
-rw-r--r--Cargo.lock63
-rw-r--r--Cargo.toml8
-rw-r--r--exporter/Cargo.toml8
-rw-r--r--exporter/src/bin/gltf.rs151
-rw-r--r--src/classes/mesh.rs41
5 files changed, 238 insertions, 33 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6870611..7279d7d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index e148c96..702632a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 {