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 .as_chunks::<3>()
82 .0
83 .iter()
84 .map(|value| Point3::new(value[0], value[1], value[2]))
85 .collect();
86 let handle = context
87 .meshes
88 .insert(TriMesh::new(positions, indices.to_vec()));
89 unsafe { out_mesh.write(AxiolidMeshHandle(handle)) };
91 AxiolidStatus::Ok
92 })
93}
94
95#[no_mangle]
99pub unsafe extern "C" fn axiolid_v0_4_mesh_counts(
100 context: AxiolidContextHandle,
101 mesh: AxiolidMeshHandle,
102 out_vertex_count: *mut usize,
103 out_triangle_count: *mut usize,
104) -> AxiolidStatus {
105 boundary(|| {
106 if out_vertex_count.is_null() || out_triangle_count.is_null() {
107 return AxiolidStatus::NullPointer;
108 }
109 let Some(context) = context_entry(context.0) else {
110 return AxiolidStatus::InvalidHandle;
111 };
112 let context = lock_unpoisoned(&context);
113 let Some(mesh) = context.meshes.get(mesh.0) else {
114 return AxiolidStatus::InvalidHandle;
115 };
116 unsafe {
118 out_vertex_count.write(mesh.positions.len());
119 out_triangle_count.write(mesh.indices.len() / 3);
120 }
121 AxiolidStatus::Ok
122 })
123}
124
125#[no_mangle]
130pub unsafe extern "C" fn axiolid_v0_4_mesh_copy(
131 context: AxiolidContextHandle,
132 mesh: AxiolidMeshHandle,
133 out_positions_xyz: *mut f64,
134 vertex_capacity: usize,
135 out_triangle_indices: *mut u32,
136 triangle_capacity: usize,
137) -> AxiolidStatus {
138 boundary(|| {
139 if (vertex_capacity != 0 && out_positions_xyz.is_null())
140 || (triangle_capacity != 0 && out_triangle_indices.is_null())
141 {
142 return AxiolidStatus::NullPointer;
143 }
144 let Some(context) = context_entry(context.0) else {
145 return AxiolidStatus::InvalidHandle;
146 };
147 let context = lock_unpoisoned(&context);
148 let Some(mesh) = context.meshes.get(mesh.0) else {
149 return AxiolidStatus::InvalidHandle;
150 };
151 let triangle_count = mesh.indices.len() / 3;
152 if vertex_capacity < mesh.positions.len() || triangle_capacity < triangle_count {
153 return AxiolidStatus::BufferTooSmall;
154 }
155 let Some(position_len) = mesh.positions.len().checked_mul(3) else {
156 return AxiolidStatus::LimitExceeded;
157 };
158 let positions: &mut [f64] = if position_len == 0 {
159 &mut []
160 } else {
161 unsafe { std::slice::from_raw_parts_mut(out_positions_xyz, position_len) }
164 };
165 let indices: &mut [u32] = if mesh.indices.is_empty() {
166 &mut []
167 } else {
168 unsafe { std::slice::from_raw_parts_mut(out_triangle_indices, mesh.indices.len()) }
170 };
171 for (target, point) in positions
172 .as_chunks_mut::<3>()
173 .0
174 .iter_mut()
175 .zip(&mesh.positions)
176 {
177 target.copy_from_slice(&[point.x, point.y, point.z]);
178 }
179 indices.copy_from_slice(&mesh.indices);
180 AxiolidStatus::Ok
181 })
182}
183
184#[no_mangle]
186pub extern "C" fn axiolid_v0_4_mesh_destroy(
187 context: AxiolidContextHandle,
188 mesh: AxiolidMeshHandle,
189) -> AxiolidStatus {
190 boundary(|| {
191 let Some(context) = context_entry(context.0) else {
192 return AxiolidStatus::InvalidHandle;
193 };
194 let mut context = lock_unpoisoned(&context);
195 match context.meshes.remove(mesh.0) {
196 Some(_) => AxiolidStatus::Ok,
197 None => AxiolidStatus::InvalidHandle,
198 }
199 })
200}
201
202#[repr(C)]
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub struct AxiolidTolerance {
206 pub linear: f64,
207 pub angular: f64,
208}
209
210#[repr(C)]
212#[derive(Debug, Clone, Copy, PartialEq)]
213pub struct AxiolidTransform {
214 pub columns: [f64; 16],
215}
216
217#[repr(C)]
219#[derive(Debug, Clone, Copy, PartialEq, Default)]
220pub struct AxiolidBounds {
221 pub min: [f64; 3],
222 pub max: [f64; 3],
223}
224
225#[repr(C)]
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
228pub struct AxiolidMeshAudit {
229 pub vertex_count: usize,
230 pub triangle_count: usize,
231 pub closed_two_manifold: u8,
232 pub reserved: [u8; 7],
234}
235
236#[repr(C)]
238#[derive(Debug, Clone, Copy, PartialEq, Default)]
239pub struct AxiolidMeasurements {
240 pub surface_area: f64,
241 pub signed_volume: f64,
242 pub surface_centroid: [f64; 3],
243 pub volume_centroid: [f64; 3],
244}
245
246#[no_mangle]
250pub unsafe extern "C" fn axiolid_v0_4_mesh_audit(
251 context: AxiolidContextHandle,
252 mesh: AxiolidMeshHandle,
253 tolerance_value: AxiolidTolerance,
254 out_audit: *mut AxiolidMeshAudit,
255) -> AxiolidStatus {
256 boundary(|| {
257 if out_audit.is_null() {
258 return AxiolidStatus::NullPointer;
259 }
260 let Ok(tolerance) = tolerance(tolerance_value) else {
261 return AxiolidStatus::InvalidArgument;
262 };
263 let Some(context) = context_entry(context.0) else {
264 return AxiolidStatus::InvalidHandle;
265 };
266 let context = lock_unpoisoned(&context);
267 let Some(mesh) = context.meshes.get(mesh.0) else {
268 return AxiolidStatus::InvalidHandle;
269 };
270 let mesh = mesh.clone();
271 let health = match context.application.validate_mesh(&mesh, tolerance) {
272 Ok(health) => health,
273 Err(error) => return record_application_error(context, error),
274 };
275 let result = AxiolidMeshAudit {
276 vertex_count: mesh.positions.len(),
277 triangle_count: mesh.indices.len() / 3,
278 closed_two_manifold: u8::from(health.is_closed_two_manifold()),
279 reserved: [0; 7],
280 };
281 unsafe { out_audit.write(result) };
283 AxiolidStatus::Ok
284 })
285}
286
287#[no_mangle]
291pub unsafe extern "C" fn axiolid_v0_4_mesh_bounds(
292 context: AxiolidContextHandle,
293 mesh: AxiolidMeshHandle,
294 out_bounds: *mut AxiolidBounds,
295) -> AxiolidStatus {
296 boundary(|| {
297 if out_bounds.is_null() {
298 return AxiolidStatus::NullPointer;
299 }
300 let Some(context) = context_entry(context.0) else {
301 return AxiolidStatus::InvalidHandle;
302 };
303 let context = lock_unpoisoned(&context);
304 let Some(mesh) = context.meshes.get(mesh.0) else {
305 return AxiolidStatus::InvalidHandle;
306 };
307 let Some(first) = mesh.positions.first() else {
308 return AxiolidStatus::InvalidArgument;
309 };
310 let mut min = [first.x, first.y, first.z];
311 let mut max = min;
312 for point in &mesh.positions[1..] {
313 min[0] = min[0].min(point.x);
314 min[1] = min[1].min(point.y);
315 min[2] = min[2].min(point.z);
316 max[0] = max[0].max(point.x);
317 max[1] = max[1].max(point.y);
318 max[2] = max[2].max(point.z);
319 }
320 unsafe { out_bounds.write(AxiolidBounds { min, max }) };
322 AxiolidStatus::Ok
323 })
324}
325
326#[no_mangle]
330pub unsafe extern "C" fn axiolid_v0_4_mesh_measure(
331 context: AxiolidContextHandle,
332 mesh: AxiolidMeshHandle,
333 tolerance_value: AxiolidTolerance,
334 out_measurements: *mut AxiolidMeasurements,
335) -> AxiolidStatus {
336 boundary(|| {
337 if out_measurements.is_null() {
338 return AxiolidStatus::NullPointer;
339 }
340 let Ok(tolerance) = tolerance(tolerance_value) else {
341 return AxiolidStatus::InvalidArgument;
342 };
343 let Some(context) = context_entry(context.0) else {
344 return AxiolidStatus::InvalidHandle;
345 };
346 let context = lock_unpoisoned(&context);
347 let Some(mesh) = context.meshes.get(mesh.0) else {
348 return AxiolidStatus::InvalidHandle;
349 };
350 let mesh = mesh.clone();
351 let measurements = match context.application.measure_mesh(&mesh, tolerance) {
352 Ok(measurements) => measurements,
353 Err(error) => return record_application_error(context, error),
354 };
355 let result = AxiolidMeasurements {
356 surface_area: measurements.surface.area,
357 signed_volume: measurements.volume.signed_volume,
358 surface_centroid: [
359 measurements.surface.centroid.x,
360 measurements.surface.centroid.y,
361 measurements.surface.centroid.z,
362 ],
363 volume_centroid: [
364 measurements.volume.centroid.x,
365 measurements.volume.centroid.y,
366 measurements.volume.centroid.z,
367 ],
368 };
369 unsafe { out_measurements.write(result) };
371 AxiolidStatus::Ok
372 })
373}
374
375#[no_mangle]
379pub unsafe extern "C" fn axiolid_v0_4_mesh_transform(
380 context: AxiolidContextHandle,
381 mesh: AxiolidMeshHandle,
382 transform: *const AxiolidTransform,
383) -> AxiolidStatus {
384 boundary(|| {
385 if transform.is_null() {
386 return AxiolidStatus::NullPointer;
387 }
388 let transform = unsafe { transform.read() };
390 let m = transform.columns;
391 if m.iter().any(|value| !value.is_finite())
392 || m[3] != 0.0
393 || m[7] != 0.0
394 || m[11] != 0.0
395 || m[15] != 1.0
396 {
397 return AxiolidStatus::InvalidArgument;
398 }
399 let Some(context) = context_entry(context.0) else {
400 return AxiolidStatus::InvalidHandle;
401 };
402 let mut context = lock_unpoisoned(&context);
403 let Some(source) = context.meshes.get(mesh.0) else {
404 return AxiolidStatus::InvalidHandle;
405 };
406 let mut transformed = Vec::with_capacity(source.positions.len());
407 for point in &source.positions {
408 let [x, y, z] = [point.x, point.y, point.z];
409 let point = Point3::new(
410 m[0].mul_add(x, m[4].mul_add(y, m[8].mul_add(z, m[12]))),
411 m[1].mul_add(x, m[5].mul_add(y, m[9].mul_add(z, m[13]))),
412 m[2].mul_add(x, m[6].mul_add(y, m[10].mul_add(z, m[14]))),
413 );
414 if !point.is_finite() {
415 return record_error(
416 context,
417 AxiolidStatus::InvalidArgument,
418 AxiolidOperation::Healing,
419 AxiolidTolerance {
420 linear: 0.0,
421 angular: 0.0,
422 },
423 "axiolid-capi",
424 "transform produced non-finite coordinates",
425 );
426 }
427 transformed.push(point);
428 }
429 let Some(mesh) = context.meshes.get_mut(mesh.0) else {
430 return AxiolidStatus::InvalidHandle;
431 };
432 mesh.positions = transformed;
433 AxiolidStatus::Ok
434 })
435}
436
437#[repr(transparent)]
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
440pub struct AxiolidResultHandle(pub u64);
441
442impl AxiolidResultHandle {
443 pub const INVALID: Self = Self(0);
444}
445
446#[non_exhaustive]
448#[repr(i32)]
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum AxiolidGeometryKind {
451 TriangleMesh = 1,
452 ExactBrep = 2,
453 HiddenLines = 3,
454 VisibleFaces = 4,
455 Unknown = 255,
456}