1use axiolid::application::{ApplicationError, ApplicationErrorSource};
11use axiolid::contracts::{ExecutionOptions, GeomError};
12use axiolid::core::{Aabb2, Frame3, Point2, Vec3};
13use axiolid::mesh_hidden_lines::{
14 EdgeKind, EdgeSelection, FaceGroup, HiddenLineLimits, HiddenLineMesh, HiddenLineRequest,
15 MeshId, MeshRole, View, Visibility,
16};
17
18use super::*;
19
20#[repr(C)]
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct AxiolidViewFrame {
24 pub origin: [f64; 3],
25 pub x: [f64; 3],
26 pub y: [f64; 3],
27 pub z: [f64; 3],
28}
29
30pub type AxiolidProjection = i32;
32pub const AXIOLID_PROJECTION_ORTHOGRAPHIC: AxiolidProjection = 1;
33
34#[repr(C)]
37#[derive(Debug, Clone, Copy, PartialEq)]
38pub struct AxiolidView {
39 pub frame: AxiolidViewFrame,
40 pub near: f64,
41 pub far: f64,
42 pub projection: AxiolidProjection,
43 pub has_near: u8,
44 pub has_far: u8,
45 pub reserved: [u8; 2],
47}
48
49#[repr(C)]
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct AxiolidImageBox {
53 pub min: [f64; 2],
54 pub max: [f64; 2],
55}
56
57pub type AxiolidMeshRole = i32;
59pub const AXIOLID_MESH_ROLE_SOLID: AxiolidMeshRole = 1;
61pub const AXIOLID_MESH_ROLE_SHEET: AxiolidMeshRole = 2;
63
64pub type AxiolidGroupMode = i32;
66pub const AXIOLID_GROUP_MODE_SINGLE: AxiolidGroupMode = 1;
68pub const AXIOLID_GROUP_MODE_PER_TRIANGLE: AxiolidGroupMode = 2;
70pub const AXIOLID_GROUP_MODE_PLANAR: AxiolidGroupMode = 3;
72
73#[repr(C)]
75#[derive(Debug, Clone, Copy)]
76pub struct AxiolidViewMesh {
77 pub mesh: AxiolidMeshHandle,
78 pub id: u64,
80 pub groups: *const u32,
81 pub group_count: usize,
82 pub role: AxiolidMeshRole,
83 pub group_mode: AxiolidGroupMode,
84}
85
86#[repr(C)]
89#[derive(Debug, Clone, Copy, PartialEq)]
90pub struct AxiolidEdgeSelection {
91 pub crease_cosine: f64,
92 pub has_crease: u8,
93 pub boundary: u8,
94 pub silhouette: u8,
95 pub hidden: u8,
96 pub group_boundary: u8,
97 pub reserved: [u8; 3],
99}
100
101#[repr(C)]
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct AxiolidHiddenLineLimits {
105 pub max_meshes: usize,
106 pub max_source_vertices: usize,
107 pub max_source_triangles: usize,
108 pub max_candidate_pairs: usize,
109 pub max_output_segments: usize,
110}
111
112#[non_exhaustive]
114#[repr(i32)]
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum AxiolidEdgeKind {
117 Boundary = 1,
118 Silhouette = 2,
119 Crease = 3,
120 GroupBoundary = 4,
121 Unknown = 255,
122}
123
124#[non_exhaustive]
126#[repr(i32)]
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum AxiolidVisibility {
129 Visible = 1,
130 Hidden = 2,
131 Unknown = 255,
132}
133
134#[repr(C)]
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub struct AxiolidHiddenLineSegment {
138 pub mesh_id: u64,
139 pub vertices: [u32; 2],
141 pub start: [f64; 2],
142 pub end: [f64; 2],
143 pub kind: AxiolidEdgeKind,
144 pub visibility: AxiolidVisibility,
145}
146
147#[repr(C)]
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub struct AxiolidHiddenLineEvidence {
151 pub source_meshes: usize,
152 pub source_triangles: usize,
153 pub selected_edges: usize,
154 pub point_edges: usize,
155 pub clipped_edges: usize,
156 pub candidate_pairs: usize,
157 pub exact_escalations: usize,
158 pub collapsed_segments: usize,
159 pub visible_segments: usize,
160 pub hidden_segments: usize,
161}
162
163fn edge_kind(value: EdgeKind) -> AxiolidEdgeKind {
164 match value {
165 EdgeKind::Boundary => AxiolidEdgeKind::Boundary,
166 EdgeKind::Silhouette => AxiolidEdgeKind::Silhouette,
167 EdgeKind::Crease => AxiolidEdgeKind::Crease,
168 EdgeKind::GroupBoundary => AxiolidEdgeKind::GroupBoundary,
169 _ => AxiolidEdgeKind::Unknown,
170 }
171}
172
173fn visibility(value: Visibility) -> AxiolidVisibility {
174 match value {
175 Visibility::Visible => AxiolidVisibility::Visible,
176 Visibility::Hidden => AxiolidVisibility::Hidden,
177 _ => AxiolidVisibility::Unknown,
178 }
179}
180
181pub(crate) struct Refusal {
183 status: AxiolidStatus,
184 mesh: Option<u64>,
185 message: &'static str,
186}
187
188impl Refusal {
189 pub(crate) const fn invalid(message: &'static str) -> Self {
190 Self {
191 status: AxiolidStatus::InvalidArgument,
192 mesh: None,
193 message,
194 }
195 }
196
197 pub(crate) const fn mesh(status: AxiolidStatus, mesh: u64, message: &'static str) -> Self {
198 Self {
199 status,
200 mesh: Some(mesh),
201 message,
202 }
203 }
204}
205
206pub(crate) fn flag(value: u8, message: &'static str) -> Result<bool, Refusal> {
207 match value {
208 0 => Ok(false),
209 1 => Ok(true),
210 _ => Err(Refusal::invalid(message)),
211 }
212}
213
214pub(crate) fn finite(values: &[f64], message: &'static str) -> Result<(), Refusal> {
215 if values.iter().all(|value| value.is_finite()) {
216 Ok(())
217 } else {
218 Err(Refusal::invalid(message))
219 }
220}
221
222fn vec3(value: [f64; 3]) -> Vec3 {
223 Vec3::new(value[0], value[1], value[2])
224}
225
226pub(crate) fn read_view(view: &AxiolidView) -> Result<View, Refusal> {
227 if view.projection != AXIOLID_PROJECTION_ORTHOGRAPHIC {
228 return Err(Refusal::invalid("unknown view projection"));
229 }
230 if view.reserved != [0; 2] {
231 return Err(Refusal::invalid("view reserved bytes must be zero"));
232 }
233 let frame = &view.frame;
234 for axis in [&frame.origin, &frame.x, &frame.y, &frame.z] {
235 finite(axis, "view frame must be finite")?;
236 }
237 finite(&[view.near, view.far], "view depth range must be finite")?;
238 let has_near = flag(view.has_near, "has_near must be 0 or 1")?;
239 let has_far = flag(view.has_far, "has_far must be 0 or 1")?;
240 let mut result = View::new(Frame3 {
241 origin: vec3(frame.origin),
242 x: vec3(frame.x),
243 y: vec3(frame.y),
244 z: vec3(frame.z),
245 });
246 if has_near {
247 result = result.with_near(view.near);
248 }
249 if has_far {
250 result = result.with_far(view.far);
251 }
252 Ok(result)
253}
254
255fn read_edges(edges: &AxiolidEdgeSelection) -> Result<EdgeSelection, Refusal> {
256 if edges.reserved != [0; 3] {
257 return Err(Refusal::invalid(
258 "edge selection reserved bytes must be zero",
259 ));
260 }
261 finite(&[edges.crease_cosine], "crease cosine must be finite")?;
262 let has_crease = flag(edges.has_crease, "has_crease must be 0 or 1")?;
263 Ok(
264 EdgeSelection::new(has_crease.then_some(edges.crease_cosine))
265 .with_boundary(flag(edges.boundary, "boundary must be 0 or 1")?)
266 .with_silhouette(flag(edges.silhouette, "silhouette must be 0 or 1")?)
267 .with_hidden(flag(edges.hidden, "hidden must be 0 or 1")?)
268 .with_group_boundary(flag(edges.group_boundary, "group_boundary must be 0 or 1")?),
269 )
270}
271
272pub(crate) fn read_region(region: &AxiolidImageBox) -> Result<Aabb2, Refusal> {
273 finite(&[region.min, region.max].concat(), "region must be finite")?;
274 Ok(Aabb2 {
275 min: Point2::new(region.min[0], region.min[1]),
276 max: Point2::new(region.max[0], region.max[1]),
277 })
278}
279
280pub(crate) struct DecodedMesh {
282 pub(crate) id: u64,
283 pub(crate) handle: u64,
284 pub(crate) role: MeshRole,
285 pub(crate) mode: AxiolidGroupMode,
286 pub(crate) groups: Vec<FaceGroup>,
287}
288
289pub(crate) fn read_mesh(
290 entry: &AxiolidViewMesh,
291 context: &Context,
292) -> Result<DecodedMesh, Refusal> {
293 let role = match entry.role {
294 AXIOLID_MESH_ROLE_SOLID => MeshRole::Solid,
295 AXIOLID_MESH_ROLE_SHEET => MeshRole::Sheet,
296 _ => {
297 return Err(Refusal::mesh(
298 AxiolidStatus::InvalidArgument,
299 entry.id,
300 "unknown mesh role",
301 ))
302 }
303 };
304 let Some(mesh) = context.meshes.get(entry.mesh.0) else {
305 return Err(Refusal::mesh(
306 AxiolidStatus::InvalidHandle,
307 entry.id,
308 "unknown mesh handle",
309 ));
310 };
311 let groups = match entry.group_mode {
312 AXIOLID_GROUP_MODE_SINGLE | AXIOLID_GROUP_MODE_PLANAR => {
313 if entry.group_count != 0 {
314 return Err(Refusal::mesh(
315 AxiolidStatus::InvalidArgument,
316 entry.id,
317 "group_count must be zero unless the group mode is per-triangle",
318 ));
319 }
320 Vec::new()
321 }
322 AXIOLID_GROUP_MODE_PER_TRIANGLE => {
323 if entry.group_count != mesh.indices.len() / 3 {
325 return Err(Refusal::mesh(
326 AxiolidStatus::InvalidArgument,
327 entry.id,
328 "group_count must equal the mesh's triangle count",
329 ));
330 }
331 if entry.group_count == 0 {
332 Vec::new()
333 } else if entry.groups.is_null() {
334 return Err(Refusal::mesh(
335 AxiolidStatus::NullPointer,
336 entry.id,
337 "per-triangle groups are null",
338 ));
339 } else {
340 let ids = unsafe { std::slice::from_raw_parts(entry.groups, entry.group_count) };
343 ids.iter().copied().map(FaceGroup).collect()
344 }
345 }
346 _ => {
347 return Err(Refusal::mesh(
348 AxiolidStatus::InvalidArgument,
349 entry.id,
350 "unknown group mode",
351 ))
352 }
353 };
354 Ok(DecodedMesh {
355 id: entry.id,
356 handle: entry.mesh.0,
357 role,
358 mode: entry.group_mode,
359 groups,
360 })
361}
362
363pub(crate) fn record_refusal(
364 mut context: MutexGuard<'_, Context>,
365 operation: AxiolidOperation,
366 refusal: Refusal,
367 tolerance: AxiolidTolerance,
368) -> AxiolidStatus {
369 context.last_error = Some(ErrorRecord {
370 status: refusal.status,
371 operation,
372 tolerance,
373 provider: "axiolid-capi".into(),
374 message: refusal.message.into(),
375 mesh: refusal.mesh,
376 });
377 refusal.status
378}
379
380pub(crate) fn record_drawing_error(
387 mut context: MutexGuard<'_, Context>,
388 operation: AxiolidOperation,
389 error: ApplicationError,
390) -> AxiolidStatus {
391 let (geometry, mesh) = match &*error.source {
392 ApplicationErrorSource::HiddenLines(refusal) => {
393 (Some(&refusal.error), refusal.mesh.map(|MeshId(id)| id))
394 }
395 ApplicationErrorSource::Geometry(geometry) => (Some(geometry), None),
396 _ => (None, None),
397 };
398 let status = match geometry {
399 Some(
400 GeomError::Unsupported { .. }
401 | GeomError::UnsupportedInput { .. }
402 | GeomError::Unavailable { .. },
403 ) => AxiolidStatus::Unsupported,
404 Some(GeomError::BudgetExceeded { .. }) => AxiolidStatus::LimitExceeded,
405 _ => AxiolidStatus::OperationFailed,
406 };
407 context.last_error = Some(ErrorRecord {
408 status,
409 operation,
410 tolerance: AxiolidTolerance {
411 linear: error.context.tolerance.linear(),
412 angular: error.context.tolerance.angular(),
413 },
414 provider: error.context.provider.as_str().into(),
415 message: error.to_string(),
416 mesh,
417 });
418 status
419}
420
421pub(crate) unsafe fn mesh_entries<'a>(
426 meshes: *const AxiolidViewMesh,
427 mesh_count: usize,
428) -> Result<&'a [AxiolidViewMesh], AxiolidStatus> {
429 if mesh_count == 0 {
430 return Ok(&[]);
431 }
432 let Some(bytes) = mesh_count.checked_mul(std::mem::size_of::<AxiolidViewMesh>()) else {
433 return Err(AxiolidStatus::LimitExceeded);
434 };
435 if bytes > isize::MAX as usize {
436 return Err(AxiolidStatus::LimitExceeded);
437 }
438 Ok(unsafe { std::slice::from_raw_parts(meshes, mesh_count) })
440}
441
442pub(crate) fn mesh_inputs<'a>(
444 state: &'a Context,
445 decoded: &'a [DecodedMesh],
446) -> Result<Vec<HiddenLineMesh<'a>>, Refusal> {
447 let mut inputs = Vec::with_capacity(decoded.len());
448 for entry in decoded {
449 let Some(mesh) = state.meshes.get(entry.handle) else {
450 return Err(Refusal::mesh(
451 AxiolidStatus::InvalidHandle,
452 entry.id,
453 "unknown mesh handle",
454 ));
455 };
456 let input = HiddenLineMesh::new(MeshId(entry.id), entry.role, mesh);
457 inputs.push(match entry.mode {
458 AXIOLID_GROUP_MODE_PER_TRIANGLE => input.with_groups(&entry.groups),
459 AXIOLID_GROUP_MODE_PLANAR => input.with_planar_groups(),
460 _ => input,
461 });
462 }
463 Ok(inputs)
464}
465
466#[no_mangle]
474pub unsafe extern "C" fn axiolid_v0_4_hidden_lines(
475 context: AxiolidContextHandle,
476 meshes: *const AxiolidViewMesh,
477 mesh_count: usize,
478 view: *const AxiolidView,
479 edges: *const AxiolidEdgeSelection,
480 region: *const AxiolidImageBox,
481 limits: *const AxiolidHiddenLineLimits,
482 tolerance_value: AxiolidTolerance,
483 out_result: *mut AxiolidResultHandle,
484) -> AxiolidStatus {
485 boundary(|| {
486 if out_result.is_null()
487 || view.is_null()
488 || edges.is_null()
489 || limits.is_null()
490 || (mesh_count != 0 && meshes.is_null())
491 {
492 return AxiolidStatus::NullPointer;
493 }
494 let Some(context) = context_entry(context.0) else {
495 return AxiolidStatus::InvalidHandle;
496 };
497 let mut context = lock_unpoisoned(&context);
498 if mesh_count > context.config.max_meshes as usize {
499 return AxiolidStatus::LimitExceeded;
500 }
501 let (view, edges, limits) = unsafe { (view.read(), edges.read(), limits.read()) };
503 let region = unsafe { region.as_ref() };
505 let entries = match unsafe { mesh_entries(meshes, mesh_count) } {
508 Ok(entries) => entries,
509 Err(status) => return status,
510 };
511 let Ok(tolerance) = tolerance(tolerance_value) else {
512 return record_refusal(
513 context,
514 AxiolidOperation::MeshHiddenLines,
515 Refusal::invalid("tolerance must be finite and non-negative"),
516 tolerance_value,
517 );
518 };
519 let decoded = (|| {
520 let view = read_view(&view)?;
521 let edges = read_edges(&edges)?;
522 let region = region.map(read_region).transpose()?;
523 let meshes = entries
524 .iter()
525 .map(|entry| read_mesh(entry, &context))
526 .collect::<Result<Vec<_>, _>>()?;
527 Ok((view, edges, region, meshes))
528 })();
529 let (view, edges, region, decoded) = match decoded {
530 Ok(decoded) => decoded,
531 Err(refusal) => {
532 return record_refusal(
533 context,
534 AxiolidOperation::MeshHiddenLines,
535 refusal,
536 tolerance_value,
537 )
538 }
539 };
540 let request_limits = HiddenLineLimits::new(
541 limits.max_meshes,
542 limits.max_source_vertices,
543 limits.max_source_triangles,
544 limits.max_candidate_pairs,
545 limits.max_output_segments,
546 );
547 let options = ExecutionOptions::new(tolerance);
548 let outcome = {
551 let state: &Context = &context;
552 let inputs = match mesh_inputs(state, &decoded) {
553 Ok(inputs) => inputs,
554 Err(refusal) => {
555 return record_refusal(
556 context,
557 AxiolidOperation::MeshHiddenLines,
558 refusal,
559 tolerance_value,
560 )
561 }
562 };
563 let mut request = HiddenLineRequest::new(&inputs, view, edges, request_limits);
564 if let Some(region) = region {
565 request = request.with_region(region);
566 }
567 state.application.hidden_lines(&request, &options)
568 };
569 let outcome = match outcome {
570 Ok(outcome) => outcome,
571 Err(error) => {
572 return record_drawing_error(context, AxiolidOperation::MeshHiddenLines, error)
573 }
574 };
575 let Ok(handle) = insert_result(&mut context, StoredResult::HiddenLines(outcome)) else {
576 return AxiolidStatus::LimitExceeded;
577 };
578 unsafe { out_result.write(AxiolidResultHandle(handle)) };
580 AxiolidStatus::Ok
581 })
582}
583
584fn with_outcome(
586 context: AxiolidContextHandle,
587 result: AxiolidResultHandle,
588 read: impl FnOnce(&axiolid::mesh_hidden_lines::HiddenLineOutcome) -> AxiolidStatus,
589) -> AxiolidStatus {
590 let Some(context) = context_entry(context.0) else {
591 return AxiolidStatus::InvalidHandle;
592 };
593 let context = lock_unpoisoned(&context);
594 match context.results.get(result.0) {
595 Some(StoredResult::HiddenLines(outcome)) => read(outcome),
596 Some(_) => AxiolidStatus::WrongResultKind,
597 None => AxiolidStatus::InvalidHandle,
598 }
599}
600
601#[no_mangle]
605pub unsafe extern "C" fn axiolid_v0_4_hidden_lines_count(
606 context: AxiolidContextHandle,
607 result: AxiolidResultHandle,
608 out_count: *mut usize,
609) -> AxiolidStatus {
610 boundary(|| {
611 if out_count.is_null() {
612 return AxiolidStatus::NullPointer;
613 }
614 with_outcome(context, result, |outcome| {
615 unsafe { out_count.write(outcome.segments.len()) };
617 AxiolidStatus::Ok
618 })
619 })
620}
621
622#[no_mangle]
629pub unsafe extern "C" fn axiolid_v0_4_hidden_lines_copy(
630 context: AxiolidContextHandle,
631 result: AxiolidResultHandle,
632 out: *mut AxiolidHiddenLineSegment,
633 capacity: usize,
634) -> AxiolidStatus {
635 boundary(|| {
636 if capacity != 0 && out.is_null() {
637 return AxiolidStatus::NullPointer;
638 }
639 with_outcome(context, result, |outcome| {
640 if capacity < outcome.segments.len() {
641 return AxiolidStatus::BufferTooSmall;
642 }
643 for (index, segment) in outcome.segments.iter().enumerate() {
644 let value = AxiolidHiddenLineSegment {
645 mesh_id: segment.source.mesh.0,
646 vertices: segment.source.vertices,
647 start: [segment.start.x, segment.start.y],
648 end: [segment.end.x, segment.end.y],
649 kind: edge_kind(segment.kind),
650 visibility: visibility(segment.visibility),
651 };
652 unsafe { out.add(index).write(value) };
654 }
655 AxiolidStatus::Ok
656 })
657 })
658}
659
660#[no_mangle]
664pub unsafe extern "C" fn axiolid_v0_4_hidden_lines_evidence(
665 context: AxiolidContextHandle,
666 result: AxiolidResultHandle,
667 out: *mut AxiolidHiddenLineEvidence,
668) -> AxiolidStatus {
669 boundary(|| {
670 if out.is_null() {
671 return AxiolidStatus::NullPointer;
672 }
673 with_outcome(context, result, |outcome| {
674 let evidence = &outcome.evidence;
675 let value = AxiolidHiddenLineEvidence {
676 source_meshes: evidence.source_meshes,
677 source_triangles: evidence.source_triangles,
678 selected_edges: evidence.selected_edges,
679 point_edges: evidence.point_edges,
680 clipped_edges: evidence.clipped_edges,
681 candidate_pairs: evidence.candidate_pairs,
682 exact_escalations: evidence.exact_escalations,
683 collapsed_segments: evidence.collapsed_segments,
684 visible_segments: evidence.visible_segments,
685 hidden_segments: evidence.hidden_segments,
686 };
687 unsafe { out.write(value) };
689 AxiolidStatus::Ok
690 })
691 })
692}
693
694#[no_mangle]
701pub unsafe extern "C" fn axiolid_v0_4_context_last_error_mesh(
702 context: AxiolidContextHandle,
703 out_has_mesh: *mut u8,
704 out_mesh_id: *mut u64,
705) -> AxiolidStatus {
706 boundary(|| {
707 if out_has_mesh.is_null() || out_mesh_id.is_null() {
708 return AxiolidStatus::NullPointer;
709 }
710 let Some(context) = context_entry(context.0) else {
711 return AxiolidStatus::InvalidHandle;
712 };
713 let context = lock_unpoisoned(&context);
714 let Some(error) = &context.last_error else {
715 return AxiolidStatus::NoError;
716 };
717 unsafe {
719 out_has_mesh.write(u8::from(error.mesh.is_some()));
720 out_mesh_id.write(error.mesh.unwrap_or(0));
721 }
722 AxiolidStatus::Ok
723 })
724}