Skip to main content

gltforge_unity/
lib.rs

1pub mod convert;
2pub mod error;
3pub mod mesh;
4
5use std::ffi::CStr;
6use std::os::raw::c_char;
7use std::path::Path;
8use std::sync::Arc;
9
10use mesh::{UnityIndices, UnityMesh};
11
12// ---- load / retain / release ------------------------------------------------
13
14/// Parse a glTF file and build a [`UnityMesh`] for the given node.
15///
16/// Loads all primitives of the node's mesh as submeshes. Returns an
17/// `Arc<UnityMesh>` as an opaque pointer, or null on any error.
18/// The caller must eventually pass the pointer to [`gltforge_mesh_release`].
19///
20/// # Safety
21/// `path` must be a valid, null-terminated UTF-8 string.
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn gltforge_load_mesh(
24    path: *const c_char,
25    node_idx: u32,
26) -> *const UnityMesh {
27    let result = std::panic::catch_unwind(|| {
28        let path_str = unsafe { CStr::from_ptr(path) }.to_str().ok()?;
29        let path = Path::new(path_str);
30        let base_dir = path.parent()?;
31
32        let json = std::fs::read_to_string(path).ok()?;
33        let gltf = gltforge::parser::parse(&json).ok()?;
34        let buffers = gltforge::parser::load_buffers(&gltf, base_dir).ok()?;
35        let unity_mesh = convert::build_unity_mesh(&gltf, &buffers, node_idx).ok()?;
36
37        Some(Arc::into_raw(Arc::new(unity_mesh)))
38    });
39
40    result.ok().flatten().unwrap_or(std::ptr::null())
41}
42
43/// Increment the reference count of a [`UnityMesh`] handle.
44///
45/// # Safety
46/// `ptr` must have been returned by [`gltforge_load_mesh`] and not yet fully released.
47#[unsafe(no_mangle)]
48pub unsafe extern "C" fn gltforge_mesh_retain(ptr: *const UnityMesh) {
49    if !ptr.is_null() {
50        unsafe { Arc::increment_strong_count(ptr) };
51    }
52}
53
54/// Decrement the reference count of a [`UnityMesh`] handle.
55/// Frees the underlying data when the count reaches zero.
56///
57/// # Safety
58/// `ptr` must have been returned by [`gltforge_load_mesh`] or [`gltforge_mesh_retain`].
59#[unsafe(no_mangle)]
60pub unsafe extern "C" fn gltforge_mesh_release(ptr: *const UnityMesh) {
61    if !ptr.is_null() {
62        unsafe { drop(Arc::from_raw(ptr)) };
63    }
64}
65
66// ---- metadata ---------------------------------------------------------------
67
68/// Return a pointer to the mesh name as UTF-8 bytes (not null-terminated).
69/// `out_len` receives the byte length. Use `Marshal.PtrToStringUTF8(ptr, len)` in C#.
70/// The pointer is valid as long as the handle is alive.
71///
72/// # Safety
73/// `ptr` must be a valid, non-null handle. `out_len` may be null.
74#[unsafe(no_mangle)]
75pub unsafe extern "C" fn gltforge_mesh_name(ptr: *const UnityMesh, out_len: *mut u32) -> *const u8 {
76    let name = &unsafe { &*ptr }.name;
77    if !out_len.is_null() {
78        unsafe { *out_len = name.len() as u32 };
79    }
80    name.as_ptr()
81}
82
83/// Return the total number of vertices across all submeshes.
84///
85/// # Safety
86/// `ptr` must be a valid, non-null handle.
87#[unsafe(no_mangle)]
88pub unsafe extern "C" fn gltforge_mesh_vertex_count(ptr: *const UnityMesh) -> u32 {
89    unsafe { &*ptr }.positions.len() as u32
90}
91
92/// Return the number of submeshes (one per glTF primitive).
93///
94/// # Safety
95/// `ptr` must be a valid, non-null handle.
96#[unsafe(no_mangle)]
97pub unsafe extern "C" fn gltforge_mesh_submesh_count(ptr: *const UnityMesh) -> u32 {
98    unsafe { &*ptr }.submeshes.len() as u32
99}
100
101// ---- vertex data ------------------------------------------------------------
102
103/// Return a pointer to the position data (tightly packed `[x, y, z]` floats, left-handed).
104/// `out_len` receives the total number of `float` values (`vertex_count × 3`).
105/// The pointer is valid as long as the handle is alive.
106///
107/// # Safety
108/// `ptr` must be a valid, non-null handle. `out_len` may be null.
109#[unsafe(no_mangle)]
110pub unsafe extern "C" fn gltforge_mesh_positions(
111    ptr: *const UnityMesh,
112    out_len: *mut u32,
113) -> *const f32 {
114    let positions = unsafe { &*ptr }.positions.as_flattened();
115    if !out_len.is_null() {
116        unsafe { *out_len = positions.len() as u32 };
117    }
118    positions.as_ptr()
119}
120
121// ---- index data -------------------------------------------------------------
122
123/// Returns `16` if all submesh index buffers are `u16`, `32` if they are `u32`.
124/// The format is the same for every submesh and is determined by total vertex count.
125///
126/// # Safety
127/// `ptr` must be a valid, non-null handle.
128#[unsafe(no_mangle)]
129pub unsafe extern "C" fn gltforge_mesh_index_format(ptr: *const UnityMesh) -> u32 {
130    match unsafe { &*ptr }.submeshes.first() {
131        Some(sub) => match sub.indices {
132            UnityIndices::U16(_) => 16,
133            UnityIndices::U32(_) => 32,
134        },
135        None => 16,
136    }
137}
138
139/// Return a pointer to the index data for `submesh_idx` as `u16`.
140/// `out_len` receives the number of index values.
141/// Returns null if the format is `u32` or the submesh index is out of range.
142///
143/// # Safety
144/// `ptr` must be a valid, non-null handle. `out_len` may be null.
145#[unsafe(no_mangle)]
146pub unsafe extern "C" fn gltforge_mesh_submesh_indices_u16(
147    ptr: *const UnityMesh,
148    submesh_idx: u32,
149    out_len: *mut u32,
150) -> *const u16 {
151    let mesh = unsafe { &*ptr };
152    let Some(submesh) = mesh.submeshes.get(submesh_idx as usize) else {
153        if !out_len.is_null() {
154            unsafe { *out_len = 0 };
155        }
156        return std::ptr::null();
157    };
158    match &submesh.indices {
159        UnityIndices::U16(v) => {
160            if !out_len.is_null() {
161                unsafe { *out_len = v.len() as u32 };
162            }
163            v.as_ptr()
164        }
165        UnityIndices::U32(_) => {
166            if !out_len.is_null() {
167                unsafe { *out_len = 0 };
168            }
169            std::ptr::null()
170        }
171    }
172}
173
174/// Return a pointer to the index data for `submesh_idx` as `u32`.
175/// `out_len` receives the number of index values.
176/// Returns null if the format is `u16` or the submesh index is out of range.
177///
178/// # Safety
179/// `ptr` must be a valid, non-null handle. `out_len` may be null.
180#[unsafe(no_mangle)]
181pub unsafe extern "C" fn gltforge_mesh_submesh_indices_u32(
182    ptr: *const UnityMesh,
183    submesh_idx: u32,
184    out_len: *mut u32,
185) -> *const u32 {
186    let mesh = unsafe { &*ptr };
187    let Some(submesh) = mesh.submeshes.get(submesh_idx as usize) else {
188        if !out_len.is_null() {
189            unsafe { *out_len = 0 };
190        }
191        return std::ptr::null();
192    };
193    match &submesh.indices {
194        UnityIndices::U32(v) => {
195            if !out_len.is_null() {
196                unsafe { *out_len = v.len() as u32 };
197            }
198            v.as_ptr()
199        }
200        UnityIndices::U16(_) => {
201            if !out_len.is_null() {
202                unsafe { *out_len = 0 };
203            }
204            std::ptr::null()
205        }
206    }
207}