1use super::*;
2
3#[no_mangle]
7pub unsafe extern "C" fn axiolid_v0_4_mesh_import(
8 context: AxiolidContextHandle,
9 positions_xyz: *const f64,
10 vertex_count: usize,
11 triangle_indices: *const u32,
12 triangle_count: usize,
13 out_mesh: *mut AxiolidMeshHandle,
14) -> AxiolidStatus {
15 boundary(|| {
16 if out_mesh.is_null()
17 || (vertex_count != 0 && positions_xyz.is_null())
18 || (triangle_count != 0 && triangle_indices.is_null())
19 {
20 return AxiolidStatus::NullPointer;
21 }
22 let Some(position_len) = vertex_count.checked_mul(3) else {
23 return AxiolidStatus::LimitExceeded;
24 };
25 let Some(index_len) = triangle_count.checked_mul(3) else {
26 return AxiolidStatus::LimitExceeded;
27 };
28 let Some(context) = context_entry(context.0) else {
29 return AxiolidStatus::InvalidHandle;
30 };
31 let mut context = lock_unpoisoned(&context);
32 if vertex_count > context.config.max_vertices_per_mesh as usize
33 || triangle_count > context.config.max_triangles_per_mesh as usize
34 || context.meshes.live_count() >= context.config.max_meshes as usize
35 {
36 return AxiolidStatus::LimitExceeded;
37 }
38 let positions: &[f64] = if position_len == 0 {
39 &[]
40 } else {
41 unsafe { std::slice::from_raw_parts(positions_xyz, position_len) }
44 };
45 let indices: &[u32] = if index_len == 0 {
46 &[]
47 } else {
48 unsafe { std::slice::from_raw_parts(triangle_indices, index_len) }
50 };
51 if positions.iter().any(|value| !value.is_finite()) {
52 return record_error(
53 context,
54 AxiolidStatus::InvalidArgument,
55 AxiolidOperation::Healing,
56 AxiolidTolerance {
57 linear: 0.0,
58 angular: 0.0,
59 },
60 "axiolid-capi",
61 "mesh positions must all be finite",
62 );
63 }
64 if indices
65 .iter()
66 .any(|index| usize::try_from(*index).map_or(true, |index| index >= vertex_count))
67 {
68 return record_error(
69 context,
70 AxiolidStatus::InvalidArgument,
71 AxiolidOperation::Healing,
72 AxiolidTolerance {
73 linear: 0.0,
74 angular: 0.0,
75 },
76 "axiolid-capi",
77 "triangle index is outside the imported vertex array",
78 );
79 }
80 let positions = positions
81 .chunks_exact(3)
82 .map(|value| Point3::new(value[0], value[1], value[2]))
83 .collect();
84 let handle = context
85 .meshes
86 .insert(TriMesh::new(positions, indices.to_vec()));
87 unsafe { out_mesh.write(AxiolidMeshHandle(handle)) };
89 AxiolidStatus::Ok
90 })
91}
92
93#[no_mangle]
97pub unsafe extern "C" fn axiolid_v0_4_mesh_counts(
98 context: AxiolidContextHandle,
99 mesh: AxiolidMeshHandle,
100 out_vertex_count: *mut usize,
101 out_triangle_count: *mut usize,
102) -> AxiolidStatus {
103 boundary(|| {
104 if out_vertex_count.is_null() || out_triangle_count.is_null() {
105 return AxiolidStatus::NullPointer;
106 }
107 let Some(context) = context_entry(context.0) else {
108 return AxiolidStatus::InvalidHandle;
109 };
110 let context = lock_unpoisoned(&context);
111 let Some(mesh) = context.meshes.get(mesh.0) else {
112 return AxiolidStatus::InvalidHandle;
113 };
114 unsafe {
116 out_vertex_count.write(mesh.positions.len());
117 out_triangle_count.write(mesh.indices.len() / 3);
118 }
119 AxiolidStatus::Ok
120 })
121}
122
123#[no_mangle]
128pub unsafe extern "C" fn axiolid_v0_4_mesh_copy(
129 context: AxiolidContextHandle,
130 mesh: AxiolidMeshHandle,
131 out_positions_xyz: *mut f64,
132 vertex_capacity: usize,
133 out_triangle_indices: *mut u32,
134 triangle_capacity: usize,
135) -> AxiolidStatus {
136 boundary(|| {
137 if (vertex_capacity != 0 && out_positions_xyz.is_null())
138 || (triangle_capacity != 0 && out_triangle_indices.is_null())
139 {
140 return AxiolidStatus::NullPointer;
141 }
142 let Some(context) = context_entry(context.0) else {
143 return AxiolidStatus::InvalidHandle;
144 };
145 let context = lock_unpoisoned(&context);
146 let Some(mesh) = context.meshes.get(mesh.0) else {
147 return AxiolidStatus::InvalidHandle;
148 };
149 let triangle_count = mesh.indices.len() / 3;
150 if vertex_capacity < mesh.positions.len() || triangle_capacity < triangle_count {
151 return AxiolidStatus::BufferTooSmall;
152 }
153 let Some(position_len) = mesh.positions.len().checked_mul(3) else {
154 return AxiolidStatus::LimitExceeded;
155 };
156 let positions: &mut [f64] = if position_len == 0 {
157 &mut []
158 } else {
159 unsafe { std::slice::from_raw_parts_mut(out_positions_xyz, position_len) }
162 };
163 let indices: &mut [u32] = if mesh.indices.is_empty() {
164 &mut []
165 } else {
166 unsafe { std::slice::from_raw_parts_mut(out_triangle_indices, mesh.indices.len()) }
168 };
169 for (target, point) in positions.chunks_exact_mut(3).zip(&mesh.positions) {
170 target.copy_from_slice(&[point.x, point.y, point.z]);
171 }
172 indices.copy_from_slice(&mesh.indices);
173 AxiolidStatus::Ok
174 })
175}
176
177#[no_mangle]
179pub extern "C" fn axiolid_v0_4_mesh_destroy(
180 context: AxiolidContextHandle,
181 mesh: AxiolidMeshHandle,
182) -> AxiolidStatus {
183 boundary(|| {
184 let Some(context) = context_entry(context.0) else {
185 return AxiolidStatus::InvalidHandle;
186 };
187 let mut context = lock_unpoisoned(&context);
188 match context.meshes.remove(mesh.0) {
189 Some(_) => AxiolidStatus::Ok,
190 None => AxiolidStatus::InvalidHandle,
191 }
192 })
193}
194
195#[repr(C)]
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub struct AxiolidTolerance {
199 pub linear: f64,
200 pub angular: f64,
201}
202
203#[repr(C)]
205#[derive(Debug, Clone, Copy, PartialEq)]
206pub struct AxiolidTransform {
207 pub columns: [f64; 16],
208}
209
210#[repr(C)]
212#[derive(Debug, Clone, Copy, PartialEq, Default)]
213pub struct AxiolidBounds {
214 pub min: [f64; 3],
215 pub max: [f64; 3],
216}
217
218#[repr(C)]
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
221pub struct AxiolidMeshAudit {
222 pub vertex_count: usize,
223 pub triangle_count: usize,
224 pub closed_two_manifold: u8,
225 pub reserved: [u8; 7],
227}
228
229#[repr(C)]
231#[derive(Debug, Clone, Copy, PartialEq, Default)]
232pub struct AxiolidMeasurements {
233 pub surface_area: f64,
234 pub signed_volume: f64,
235 pub surface_centroid: [f64; 3],
236 pub volume_centroid: [f64; 3],
237}
238
239#[no_mangle]
243pub unsafe extern "C" fn axiolid_v0_4_mesh_audit(
244 context: AxiolidContextHandle,
245 mesh: AxiolidMeshHandle,
246 tolerance_value: AxiolidTolerance,
247 out_audit: *mut AxiolidMeshAudit,
248) -> AxiolidStatus {
249 boundary(|| {
250 if out_audit.is_null() {
251 return AxiolidStatus::NullPointer;
252 }
253 let Ok(tolerance) = tolerance(tolerance_value) else {
254 return AxiolidStatus::InvalidArgument;
255 };
256 let Some(context) = context_entry(context.0) else {
257 return AxiolidStatus::InvalidHandle;
258 };
259 let context = lock_unpoisoned(&context);
260 let Some(mesh) = context.meshes.get(mesh.0) else {
261 return AxiolidStatus::InvalidHandle;
262 };
263 let mesh = mesh.clone();
264 let health = match context.application.validate_mesh(&mesh, tolerance) {
265 Ok(health) => health,
266 Err(error) => return record_application_error(context, error),
267 };
268 let result = AxiolidMeshAudit {
269 vertex_count: mesh.positions.len(),
270 triangle_count: mesh.indices.len() / 3,
271 closed_two_manifold: u8::from(health.is_closed_two_manifold()),
272 reserved: [0; 7],
273 };
274 unsafe { out_audit.write(result) };
276 AxiolidStatus::Ok
277 })
278}
279
280#[no_mangle]
284pub unsafe extern "C" fn axiolid_v0_4_mesh_bounds(
285 context: AxiolidContextHandle,
286 mesh: AxiolidMeshHandle,
287 out_bounds: *mut AxiolidBounds,
288) -> AxiolidStatus {
289 boundary(|| {
290 if out_bounds.is_null() {
291 return AxiolidStatus::NullPointer;
292 }
293 let Some(context) = context_entry(context.0) else {
294 return AxiolidStatus::InvalidHandle;
295 };
296 let context = lock_unpoisoned(&context);
297 let Some(mesh) = context.meshes.get(mesh.0) else {
298 return AxiolidStatus::InvalidHandle;
299 };
300 let Some(first) = mesh.positions.first() else {
301 return AxiolidStatus::InvalidArgument;
302 };
303 let mut min = [first.x, first.y, first.z];
304 let mut max = min;
305 for point in &mesh.positions[1..] {
306 min[0] = min[0].min(point.x);
307 min[1] = min[1].min(point.y);
308 min[2] = min[2].min(point.z);
309 max[0] = max[0].max(point.x);
310 max[1] = max[1].max(point.y);
311 max[2] = max[2].max(point.z);
312 }
313 unsafe { out_bounds.write(AxiolidBounds { min, max }) };
315 AxiolidStatus::Ok
316 })
317}
318
319#[no_mangle]
323pub unsafe extern "C" fn axiolid_v0_4_mesh_measure(
324 context: AxiolidContextHandle,
325 mesh: AxiolidMeshHandle,
326 tolerance_value: AxiolidTolerance,
327 out_measurements: *mut AxiolidMeasurements,
328) -> AxiolidStatus {
329 boundary(|| {
330 if out_measurements.is_null() {
331 return AxiolidStatus::NullPointer;
332 }
333 let Ok(tolerance) = tolerance(tolerance_value) else {
334 return AxiolidStatus::InvalidArgument;
335 };
336 let Some(context) = context_entry(context.0) else {
337 return AxiolidStatus::InvalidHandle;
338 };
339 let context = lock_unpoisoned(&context);
340 let Some(mesh) = context.meshes.get(mesh.0) else {
341 return AxiolidStatus::InvalidHandle;
342 };
343 let mesh = mesh.clone();
344 let measurements = match context.application.measure_mesh(&mesh, tolerance) {
345 Ok(measurements) => measurements,
346 Err(error) => return record_application_error(context, error),
347 };
348 let result = AxiolidMeasurements {
349 surface_area: measurements.surface.area,
350 signed_volume: measurements.volume.signed_volume,
351 surface_centroid: [
352 measurements.surface.centroid.x,
353 measurements.surface.centroid.y,
354 measurements.surface.centroid.z,
355 ],
356 volume_centroid: [
357 measurements.volume.centroid.x,
358 measurements.volume.centroid.y,
359 measurements.volume.centroid.z,
360 ],
361 };
362 unsafe { out_measurements.write(result) };
364 AxiolidStatus::Ok
365 })
366}
367
368#[no_mangle]
372pub unsafe extern "C" fn axiolid_v0_4_mesh_transform(
373 context: AxiolidContextHandle,
374 mesh: AxiolidMeshHandle,
375 transform: *const AxiolidTransform,
376) -> AxiolidStatus {
377 boundary(|| {
378 if transform.is_null() {
379 return AxiolidStatus::NullPointer;
380 }
381 let transform = unsafe { transform.read() };
383 let m = transform.columns;
384 if m.iter().any(|value| !value.is_finite())
385 || m[3] != 0.0
386 || m[7] != 0.0
387 || m[11] != 0.0
388 || m[15] != 1.0
389 {
390 return AxiolidStatus::InvalidArgument;
391 }
392 let Some(context) = context_entry(context.0) else {
393 return AxiolidStatus::InvalidHandle;
394 };
395 let mut context = lock_unpoisoned(&context);
396 let Some(source) = context.meshes.get(mesh.0) else {
397 return AxiolidStatus::InvalidHandle;
398 };
399 let mut transformed = Vec::with_capacity(source.positions.len());
400 for point in &source.positions {
401 let [x, y, z] = [point.x, point.y, point.z];
402 let point = Point3::new(
403 m[0].mul_add(x, m[4].mul_add(y, m[8].mul_add(z, m[12]))),
404 m[1].mul_add(x, m[5].mul_add(y, m[9].mul_add(z, m[13]))),
405 m[2].mul_add(x, m[6].mul_add(y, m[10].mul_add(z, m[14]))),
406 );
407 if !point.is_finite() {
408 return record_error(
409 context,
410 AxiolidStatus::InvalidArgument,
411 AxiolidOperation::Healing,
412 AxiolidTolerance {
413 linear: 0.0,
414 angular: 0.0,
415 },
416 "axiolid-capi",
417 "transform produced non-finite coordinates",
418 );
419 }
420 transformed.push(point);
421 }
422 let Some(mesh) = context.meshes.get_mut(mesh.0) else {
423 return AxiolidStatus::InvalidHandle;
424 };
425 mesh.positions = transformed;
426 AxiolidStatus::Ok
427 })
428}
429
430#[repr(transparent)]
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
433pub struct AxiolidResultHandle(pub u64);
434
435impl AxiolidResultHandle {
436 pub const INVALID: Self = Self(0);
437}
438
439#[repr(i32)]
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
442pub enum AxiolidGeometryKind {
443 TriangleMesh = 1,
444 ExactBrep = 2,
445 Unknown = 255,
446}