gltforge_unity/unity_mesh.rs
1use crate::{unity_gltf::UnityGltf, unity_indices::UnityIndices, unity_submesh::UnitySubMesh};
2
3/// A Unity-shaped glTF mesh: one shared vertex array with N sub-meshes (one per glTF primitive).
4/// Maps directly to a `UnityEngine.Mesh` with `subMeshCount` sub-meshes.
5pub struct UnityMesh {
6 /// Mesh name. Falls back to the glTF mesh index if the source mesh is unnamed.
7 pub name: String,
8
9 /// All vertex positions across all primitives, concatenated.
10 /// Left-handed coordinate system (X negated relative to glTF).
11 /// Tightly packed `[x, y, z]` floats — maps to `mesh.vertices`.
12 pub vertices: Vec<[f32; 3]>,
13
14 /// Vertex normals, same length as `vertices`. Empty if the source mesh has no normals.
15 /// Left-handed coordinate system (X negated relative to glTF).
16 /// Tightly packed `[x, y, z]` floats — maps to `mesh.normals`.
17 pub normals: Vec<[f32; 3]>,
18
19 /// Vertex tangents, same length as `vertices`. Empty if the source mesh has no tangents.
20 /// Left-handed coordinate system (X and W negated relative to glTF).
21 /// Tightly packed `[x, y, z, w]` floats — maps to `mesh.tangents`.
22 pub tangents: Vec<[f32; 4]>,
23
24 /// UV channels, densely packed from channel 0.
25 /// `uvs[k]` holds all vertices for `TEXCOORD_k` (V-flipped for Unity's bottom-left origin).
26 /// Only channels present on every primitive are included; the vec stops at the first absent channel.
27 /// Maps to `mesh.SetUVs(k, uvs[k])`.
28 pub uvs: Vec<Vec<[f32; 2]>>,
29
30 /// One sub-mesh per glTF primitive — maps to `mesh.SetTriangles(tris, submeshIndex)`.
31 pub sub_meshes: Vec<UnitySubMesh>,
32}
33
34/// Return the number of meshes in the document.
35///
36/// # Safety
37/// `ptr` must be a valid, non-null handle.
38#[unsafe(no_mangle)]
39pub unsafe extern "C" fn gltforge_mesh_count(ptr: *const UnityGltf) -> u32 {
40 unsafe { &*ptr }.meshes.len() as u32
41}
42
43/// Return the name of mesh `mesh_idx` as UTF-8 bytes (not null-terminated).
44/// Always non-null — unnamed meshes use their index as the name.
45/// `out_len` receives the byte length.
46///
47/// # Safety
48/// `ptr` must be a valid, non-null handle. `out_len` may be null.
49#[unsafe(no_mangle)]
50pub unsafe extern "C" fn gltforge_mesh_name(
51 ptr: *const UnityGltf,
52 mesh_idx: u32,
53 out_len: *mut u32,
54) -> *const u8 {
55 let gltf = unsafe { &*ptr };
56 match gltf.meshes.get(&mesh_idx) {
57 Some(m) => {
58 if !out_len.is_null() {
59 unsafe { *out_len = m.name.len() as u32 };
60 }
61 m.name.as_ptr()
62 }
63 None => {
64 if !out_len.is_null() {
65 unsafe { *out_len = 0 };
66 }
67 std::ptr::null()
68 }
69 }
70}
71
72/// Return the total vertex count for mesh `mesh_idx` (across all sub-meshes).
73///
74/// # Safety
75/// `ptr` must be a valid, non-null handle.
76#[unsafe(no_mangle)]
77pub unsafe extern "C" fn gltforge_mesh_vertex_count(ptr: *const UnityGltf, mesh_idx: u32) -> u32 {
78 unsafe { &*ptr }
79 .meshes
80 .get(&mesh_idx)
81 .map(|m| m.vertices.len() as u32)
82 .unwrap_or(0)
83}
84
85/// Return `16` or `32` indicating the index format for mesh `mesh_idx`.
86/// The format is uniform across all sub-meshes and is determined by total vertex count.
87///
88/// # Safety
89/// `ptr` must be a valid, non-null handle.
90#[unsafe(no_mangle)]
91pub unsafe extern "C" fn gltforge_mesh_index_format(ptr: *const UnityGltf, mesh_idx: u32) -> u32 {
92 let gltf = unsafe { &*ptr };
93 match gltf
94 .meshes
95 .get(&mesh_idx)
96 .and_then(|m| m.sub_meshes.first())
97 .map(|s| &s.indices)
98 {
99 Some(UnityIndices::U32(_)) => 32,
100 _ => 16,
101 }
102}
103
104/// Return a pointer to the normal data for mesh `mesh_idx`.
105/// `out_len` receives the total number of `f32` values (`vertex_count × 3`).
106/// Returns null with `out_len = 0` if the mesh has no normals.
107///
108/// # Safety
109/// `ptr` must be a valid, non-null handle. `out_len` may be null.
110#[unsafe(no_mangle)]
111pub unsafe extern "C" fn gltforge_mesh_normals(
112 ptr: *const UnityGltf,
113 mesh_idx: u32,
114 out_len: *mut u32,
115) -> *const f32 {
116 let gltf = unsafe { &*ptr };
117 match gltf.meshes.get(&mesh_idx) {
118 Some(m) if !m.normals.is_empty() => {
119 let flat = m.normals.as_flattened();
120 if !out_len.is_null() {
121 unsafe { *out_len = flat.len() as u32 };
122 }
123 flat.as_ptr()
124 }
125 _ => {
126 if !out_len.is_null() {
127 unsafe { *out_len = 0 };
128 }
129 std::ptr::null()
130 }
131 }
132}
133
134/// Return a pointer to the tangent data for mesh `mesh_idx`.
135/// `out_len` receives the total number of `f32` values (`vertex_count × 4`).
136/// Returns null with `out_len = 0` if the mesh has no tangents.
137///
138/// # Safety
139/// `ptr` must be a valid, non-null handle. `out_len` may be null.
140#[unsafe(no_mangle)]
141pub unsafe extern "C" fn gltforge_mesh_tangents(
142 ptr: *const UnityGltf,
143 mesh_idx: u32,
144 out_len: *mut u32,
145) -> *const f32 {
146 let gltf = unsafe { &*ptr };
147 match gltf.meshes.get(&mesh_idx) {
148 Some(m) if !m.tangents.is_empty() => {
149 let flat = m.tangents.as_flattened();
150 if !out_len.is_null() {
151 unsafe { *out_len = flat.len() as u32 };
152 }
153 flat.as_ptr()
154 }
155 _ => {
156 if !out_len.is_null() {
157 unsafe { *out_len = 0 };
158 }
159 std::ptr::null()
160 }
161 }
162}
163
164/// Return the number of UV channels for mesh `mesh_idx`.
165///
166/// # Safety
167/// `ptr` must be a valid, non-null handle.
168#[unsafe(no_mangle)]
169pub unsafe extern "C" fn gltforge_mesh_uv_channel_count(
170 ptr: *const UnityGltf,
171 mesh_idx: u32,
172) -> u32 {
173 unsafe { &*ptr }
174 .meshes
175 .get(&mesh_idx)
176 .map(|m| m.uvs.len() as u32)
177 .unwrap_or(0)
178}
179
180/// Return a pointer to the UV data for mesh `mesh_idx`, channel `channel`.
181/// `out_len` receives the total number of `f32` values (`vertex_count × 2`).
182/// Returns null with `out_len = 0` if the channel is absent.
183///
184/// # Safety
185/// `ptr` must be a valid, non-null handle. `out_len` may be null.
186#[unsafe(no_mangle)]
187pub unsafe extern "C" fn gltforge_mesh_uvs(
188 ptr: *const UnityGltf,
189 mesh_idx: u32,
190 channel: u32,
191 out_len: *mut u32,
192) -> *const f32 {
193 let gltf = unsafe { &*ptr };
194 match gltf
195 .meshes
196 .get(&mesh_idx)
197 .and_then(|m| m.uvs.get(channel as usize))
198 {
199 Some(ch) if !ch.is_empty() => {
200 let flat = ch.as_flattened();
201 if !out_len.is_null() {
202 unsafe { *out_len = flat.len() as u32 };
203 }
204 flat.as_ptr()
205 }
206 _ => {
207 if !out_len.is_null() {
208 unsafe { *out_len = 0 };
209 }
210 std::ptr::null()
211 }
212 }
213}
214
215/// Return a pointer to the position data for mesh `mesh_idx`.
216/// `out_len` receives the total number of `f32` values (`vertex_count × 3`).
217///
218/// # Safety
219/// `ptr` must be a valid, non-null handle. `out_len` may be null.
220#[unsafe(no_mangle)]
221pub unsafe extern "C" fn gltforge_mesh_positions(
222 ptr: *const UnityGltf,
223 mesh_idx: u32,
224 out_len: *mut u32,
225) -> *const f32 {
226 let gltf = unsafe { &*ptr };
227 match gltf.meshes.get(&mesh_idx) {
228 Some(m) => {
229 let flat = m.vertices.as_flattened();
230 if !out_len.is_null() {
231 unsafe { *out_len = flat.len() as u32 };
232 }
233 flat.as_ptr()
234 }
235 None => {
236 if !out_len.is_null() {
237 unsafe { *out_len = 0 };
238 }
239 std::ptr::null()
240 }
241 }
242}