gltforge_unity/
convert.rs1use std::panic::Location;
2
3use error_location::ErrorLocation;
4use gltforge::parser::resolve_accessor;
5use gltforge::schema::{AccessorComponentType, AccessorType, Gltf, MeshPrimitiveMode};
6
7use crate::error::{ConvertError, ConvertResult};
8use crate::mesh::{UnityIndices, UnityMesh, UnitySubmesh};
9
10#[track_caller]
17pub fn build_unity_mesh(
18 gltf: &Gltf,
19 buffers: &[Vec<u8>],
20 node_idx: u32,
21) -> ConvertResult<UnityMesh> {
22 let nodes = gltf.nodes.as_deref().ok_or_else(|| ConvertError::NoNodes {
25 location: ErrorLocation::from(Location::caller()),
26 })?;
27
28 let node = nodes
29 .get(node_idx as usize)
30 .ok_or_else(|| ConvertError::NodeIndexOutOfRange {
31 index: node_idx as usize,
32 location: ErrorLocation::from(Location::caller()),
33 })?;
34
35 let mesh_idx = node.mesh.ok_or_else(|| ConvertError::NodeHasNoMesh {
36 index: node_idx as usize,
37 location: ErrorLocation::from(Location::caller()),
38 })?;
39
40 let meshes = gltf
43 .meshes
44 .as_deref()
45 .ok_or_else(|| ConvertError::NoMeshes {
46 location: ErrorLocation::from(Location::caller()),
47 })?;
48
49 let mesh = meshes
50 .get(mesh_idx as usize)
51 .ok_or_else(|| ConvertError::MeshIndexOutOfRange {
52 index: mesh_idx as usize,
53 location: ErrorLocation::from(Location::caller()),
54 })?;
55
56 let bvs = gltf.buffer_views.as_deref().unwrap_or(&[]);
57 let accessors = gltf.accessors.as_deref().unwrap_or(&[]);
58
59 let mut all_positions: Vec<[f32; 3]> = Vec::new();
62 let mut raw_submeshes: Vec<Vec<u32>> = Vec::new();
63
64 for prim in &mesh.primitives {
65 if prim.mode != MeshPrimitiveMode::Triangles {
66 return Err(ConvertError::UnsupportedPrimitiveMode {
67 mode: prim.mode,
68 location: ErrorLocation::from(Location::caller()),
69 });
70 }
71
72 let vertex_offset = all_positions.len() as u32;
73
74 let pos_id =
76 *prim
77 .attributes
78 .get("POSITION")
79 .ok_or_else(|| ConvertError::NoPositionAttribute {
80 location: ErrorLocation::from(Location::caller()),
81 })? as usize;
82
83 let pos_acc =
84 accessors
85 .get(pos_id)
86 .ok_or_else(|| ConvertError::PositionAccessorOutOfRange {
87 location: ErrorLocation::from(Location::caller()),
88 })?;
89
90 if pos_acc.accessor_type != AccessorType::Vec3
91 || pos_acc.component_type != AccessorComponentType::Float
92 {
93 return Err(ConvertError::InvalidPositionType {
94 location: ErrorLocation::from(Location::caller()),
95 });
96 }
97
98 let pos_bytes =
99 resolve_accessor(pos_acc, bvs, buffers).map_err(|e| ConvertError::Resolve {
100 source: e,
101 location: ErrorLocation::from(Location::caller()),
102 })?;
103
104 all_positions.extend(pos_bytes.chunks_exact(12).map(|c| {
105 let x = f32::from_le_bytes([c[0], c[1], c[2], c[3]]);
106 let y = f32::from_le_bytes([c[4], c[5], c[6], c[7]]);
107 let z = f32::from_le_bytes([c[8], c[9], c[10], c[11]]);
108 [-x, y, z]
109 }));
110
111 let idx_id = prim.indices.ok_or_else(|| ConvertError::NoIndices {
113 location: ErrorLocation::from(Location::caller()),
114 })?;
115
116 let idx_acc = accessors.get(idx_id as usize).ok_or_else(|| {
117 ConvertError::IndexAccessorOutOfRange {
118 location: ErrorLocation::from(Location::caller()),
119 }
120 })?;
121
122 let idx_bytes =
123 resolve_accessor(idx_acc, bvs, buffers).map_err(|e| ConvertError::Resolve {
124 source: e,
125 location: ErrorLocation::from(Location::caller()),
126 })?;
127
128 let raw = decode_indices(idx_bytes, idx_acc.component_type)?;
129
130 let wound: Vec<u32> = raw
132 .chunks_exact(3)
133 .flat_map(|tri| {
134 [
135 tri[0] + vertex_offset,
136 tri[2] + vertex_offset,
137 tri[1] + vertex_offset,
138 ]
139 })
140 .collect();
141
142 raw_submeshes.push(wound);
143 }
144
145 let total_vertices = all_positions.len();
148 let use_u16 = total_vertices <= 65535;
149
150 let submeshes: Vec<UnitySubmesh> = raw_submeshes
151 .into_iter()
152 .map(|wound| UnitySubmesh {
153 indices: if use_u16 {
154 UnityIndices::U16(wound.into_iter().map(|i| i as u16).collect())
155 } else {
156 UnityIndices::U32(wound)
157 },
158 })
159 .collect();
160
161 let name = derive_mesh_name(
164 node.name.as_deref(),
165 mesh.name.as_deref(),
166 node_idx,
167 mesh_idx,
168 mesh.primitives.len(),
169 );
170
171 Ok(UnityMesh {
172 name,
173 positions: all_positions,
174 submeshes,
175 })
176}
177
178fn derive_mesh_name(
179 node_name: Option<&str>,
180 mesh_name: Option<&str>,
181 node_idx: u32,
182 mesh_idx: u32,
183 prim_count: usize,
184) -> String {
185 match (node_name, mesh_name) {
186 (Some(n), Some(m)) => format!("{n}_{m}"),
187 (Some(n), None) if prim_count == 1 => n.to_string(),
188 (Some(n), None) => format!("{n}_{mesh_idx}"),
189 (None, _) => format!("{node_idx}_{mesh_idx}"),
190 }
191}
192
193#[track_caller]
195fn decode_indices(bytes: &[u8], component_type: AccessorComponentType) -> ConvertResult<Vec<u32>> {
196 match component_type {
197 AccessorComponentType::UnsignedByte => Ok(bytes.iter().map(|&b| b as u32).collect()),
198 AccessorComponentType::UnsignedShort => Ok(bytes
199 .chunks_exact(2)
200 .map(|c| u16::from_le_bytes([c[0], c[1]]) as u32)
201 .collect()),
202 AccessorComponentType::UnsignedInt => Ok(bytes
203 .chunks_exact(4)
204 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
205 .collect()),
206 other => Err(ConvertError::UnsupportedIndexComponentType {
207 component_type: other,
208 location: ErrorLocation::from(Location::caller()),
209 }),
210 }
211}