1use std::collections::BTreeMap;
9use crate::linalg::{Vec2, Vec3, Vec4, Mat3, Mat3x4};
10use crate::math;
11
12#[path = "types_bounds.rs"]
13mod types_bounds;
14pub use types_bounds::{Box, Rect};
15
16#[path = "types_meshgl.rs"]
17mod types_meshgl;
18pub use types_meshgl::{MeshGLP, MeshGL, MeshGL64, MeshIndex, MeshPrecision};
19
20pub const K_PI: f64 = std::f64::consts::PI;
25pub const K_TWO_PI: f64 = std::f64::consts::TAU;
26pub const K_HALF_PI: f64 = std::f64::consts::FRAC_PI_2;
27pub const K_PRECISION: f64 = 1e-12;
29
30pub const DEFAULT_SEGMENTS: i32 = 0;
31pub const DEFAULT_ANGLE: f64 = 10.0;
32pub const DEFAULT_LENGTH: f64 = 1.0;
33
34#[inline]
39pub fn radians(a: f64) -> f64 {
40 a * K_PI / 180.0
41}
42
43#[inline]
44pub fn degrees(a: f64) -> f64 {
45 a * 180.0 / K_PI
46}
47
48#[inline]
50pub fn smoothstep(edge0: f64, edge1: f64, a: f64) -> f64 {
51 let x = ((a - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
52 x * x * (3.0 - 2.0 * x)
53}
54
55pub fn sind(x: f64) -> f64 {
64 if !x.is_finite() {
65 return f64::NAN;
66 }
67 if x < 0.0 {
68 return -sind(-x);
69 }
70 let mut quo = (x / 90.0).round_ties_even() as i64;
75 let mut r = x - quo as f64 * 90.0;
76 if r > 45.0 {
77 quo += 1;
78 r -= 90.0;
79 } else if r < -45.0 {
80 quo -= 1;
81 r += 90.0;
82 } else if r == 45.0 && quo % 2 != 0 {
83 quo += 1;
85 r = -45.0;
86 } else if r == -45.0 && quo % 2 != 0 {
87 quo -= 1;
88 r = 45.0;
89 }
90 match ((quo % 4) + 4) % 4 {
91 0 => math::sin(radians(r)),
92 1 => math::cos(radians(r)),
93 2 => -math::sin(radians(r)),
94 3 => -math::cos(radians(r)),
95 _ => 0.0,
96 }
97}
98
99#[inline]
101pub fn cosd(x: f64) -> f64 {
102 sind(x + 90.0)
103}
104
105pub type SimplePolygon = Vec<Vec2>;
111
112pub type Polygons = Vec<SimplePolygon>;
114
115#[derive(Clone, Copy, Debug, PartialEq)]
117pub struct PolyVert {
118 pub pos: Vec2,
119 pub idx: i32,
120}
121
122pub type SimplePolygonIdx = Vec<PolyVert>;
124
125pub type PolygonsIdx = Vec<SimplePolygonIdx>;
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
133pub enum OpType {
134 Add,
135 Subtract,
136 Intersect,
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
144pub enum Error {
145 NoError,
146 NonFiniteVertex,
147 NotManifold,
148 VertexOutOfBounds,
149 PropertiesWrongLength,
150 MissingPositionProperties,
151 MergeVectorsDifferentLengths,
152 MergeIndexOutOfBounds,
153 TransformWrongLength,
154 RunIndexWrongLength,
155 FaceIdWrongLength,
156 InvalidConstruction,
157 ResultTooLarge,
158 InvalidTangents,
159 Cancelled,
167 NotClosed,
172}
173
174impl Error {
175 pub fn to_str(self) -> &'static str {
176 match self {
177 Error::NoError => "No Error",
178 Error::NonFiniteVertex => "Non-Finite Vertex",
179 Error::NotManifold => "Not Manifold",
180 Error::VertexOutOfBounds => "Vertex Out of Bounds",
181 Error::PropertiesWrongLength => "Properties Wrong Length",
182 Error::MissingPositionProperties => "Missing Position Properties",
183 Error::MergeVectorsDifferentLengths => "Merge Vectors Different Lengths",
184 Error::MergeIndexOutOfBounds => "Merge Index Out of Bounds",
185 Error::TransformWrongLength => "Transform Wrong Length",
186 Error::RunIndexWrongLength => "Run Index Wrong Length",
187 Error::FaceIdWrongLength => "Face ID Wrong Length",
188 Error::InvalidConstruction => "Invalid Construction",
189 Error::ResultTooLarge => "Result Too Large",
190 Error::InvalidTangents => "Invalid Tangents",
191 Error::Cancelled => "Cancelled",
192 Error::NotClosed => "Not Closed",
193 }
194 }
195}
196
197impl std::fmt::Display for Error {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 write!(f, "{}", self.to_str())
200 }
201}
202
203#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
212pub enum BooleanEngine {
213 #[default]
216 Exact,
217 Robust,
221 Auto,
224}
225
226static BOOLEAN_ENGINE_DEFAULT: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
227
228pub struct BooleanConfig;
233
234impl BooleanConfig {
235 pub fn set_default_engine(engine: BooleanEngine) {
236 let v = match engine {
237 BooleanEngine::Exact => 0u8,
238 BooleanEngine::Robust => 1,
239 BooleanEngine::Auto => 2,
240 };
241 BOOLEAN_ENGINE_DEFAULT.store(v, std::sync::atomic::Ordering::Relaxed);
242 }
243
244 pub fn default_engine() -> BooleanEngine {
245 match BOOLEAN_ENGINE_DEFAULT.load(std::sync::atomic::Ordering::Relaxed) {
246 1 => BooleanEngine::Robust,
247 2 => BooleanEngine::Auto,
248 _ => BooleanEngine::Exact,
249 }
250 }
251
252 pub fn reset_to_defaults() {
253 Self::set_default_engine(BooleanEngine::Exact);
254 }
255}
256
257use std::sync::OnceLock;
262use std::sync::Mutex;
263
264struct QualityState {
265 min_circular_angle: f64,
266 min_circular_edge_length: f64,
267 circular_segments: i32,
268}
269
270static QUALITY_STATE: OnceLock<Mutex<QualityState>> = OnceLock::new();
271
272fn quality_state() -> &'static Mutex<QualityState> {
273 QUALITY_STATE.get_or_init(|| {
274 Mutex::new(QualityState {
275 min_circular_angle: DEFAULT_ANGLE,
276 min_circular_edge_length: DEFAULT_LENGTH,
277 circular_segments: DEFAULT_SEGMENTS,
278 })
279 })
280}
281
282pub struct Quality;
283
284impl Quality {
285 pub fn set_min_circular_angle(angle: f64) {
286 quality_state().lock().unwrap().min_circular_angle = angle;
287 }
288
289 pub fn set_min_circular_edge_length(length: f64) {
290 quality_state().lock().unwrap().min_circular_edge_length = length;
291 }
292
293 pub fn set_circular_segments(n: i32) {
294 quality_state().lock().unwrap().circular_segments = n;
295 }
296
297 pub fn get_circular_segments(radius: f64) -> i32 {
298 let q = quality_state().lock().unwrap();
299 if q.circular_segments > 0 {
300 return q.circular_segments;
301 }
302 let n_seg_a = (360.0 / q.min_circular_angle) as i32;
304 let n_seg_l = (2.0 * radius.abs() * K_PI / q.min_circular_edge_length) as i32;
305 let mut n_seg = n_seg_a.min(n_seg_l) + 3;
306 n_seg -= n_seg % 4;
307 n_seg.max(4)
308 }
309
310 pub fn reset_to_defaults() {
311 let mut q = quality_state().lock().unwrap();
312 q.min_circular_angle = DEFAULT_ANGLE;
313 q.min_circular_edge_length = DEFAULT_LENGTH;
314 q.circular_segments = DEFAULT_SEGMENTS;
315 }
316}
317
318#[derive(Clone, Debug)]
323pub struct ExecutionParams {
324 pub intermediate_checks: bool,
325 pub self_intersection_checks: bool,
326 pub process_overlaps: bool,
327 pub suppress_errors: bool,
328 pub cleanup_triangles: bool,
329 pub verbose: i32,
330}
331
332impl Default for ExecutionParams {
333 fn default() -> Self {
334 ExecutionParams {
335 intermediate_checks: false,
336 self_intersection_checks: false,
337 process_overlaps: true,
338 suppress_errors: false,
339 cleanup_triangles: true,
340 verbose: 0,
341 }
342 }
343}
344
345#[derive(Clone, Copy, Debug, PartialEq)]
350pub struct Smoothness {
351 pub halfedge: usize,
353 pub smoothness: f64,
355}
356
357#[derive(Clone, Debug, Default)]
363pub struct RayHit {
364 pub face_id: u64,
366 pub distance: f64,
369 pub position: Vec3,
371 pub normal: Vec3,
373}
374
375#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
380pub struct Halfedge {
381 pub start_vert: i32,
382 pub end_vert: i32,
383 pub paired_halfedge: i32,
384 pub prop_vert: i32,
385}
386
387impl Halfedge {
388 pub fn is_forward(&self) -> bool {
389 self.start_vert < self.end_vert
390 }
391}
392
393impl PartialOrd for Halfedge {
394 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
395 Some(self.cmp(other))
396 }
397}
398
399impl Ord for Halfedge {
400 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
401 if self.start_vert == other.start_vert {
402 self.end_vert.cmp(&other.end_vert)
403 } else {
404 self.start_vert.cmp(&other.start_vert)
405 }
406 }
407}
408
409#[derive(Clone, Copy, Debug, PartialEq)]
414pub struct Barycentric {
415 pub tri: i32,
416 pub uvw: Vec4,
417}
418
419#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
424pub struct TriRef {
425 pub mesh_id: i32,
427 pub original_id: i32,
429 pub face_id: i32,
431 pub coplanar_id: i32,
433}
434
435impl TriRef {
436 pub fn same_face(&self, other: &TriRef) -> bool {
437 self.mesh_id == other.mesh_id
438 && self.coplanar_id == other.coplanar_id
439 && self.face_id == other.face_id
440 }
441}
442
443#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
448pub struct TmpEdge {
449 pub first: i32,
450 pub second: i32,
451 pub halfedge_idx: i32,
452}
453
454impl TmpEdge {
455 pub fn new(start: i32, end: i32, idx: i32) -> Self {
456 TmpEdge {
457 first: start.min(end),
458 second: start.max(end),
459 halfedge_idx: idx,
460 }
461 }
462}
463
464impl PartialOrd for TmpEdge {
465 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
466 Some(self.cmp(other))
467 }
468}
469
470impl Ord for TmpEdge {
471 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
472 if self.first == other.first {
473 self.second.cmp(&other.second)
474 } else {
475 self.first.cmp(&other.first)
476 }
477 }
478}
479
480#[derive(Clone, Debug)]
486pub struct Relation {
487 pub original_id: i32,
488 pub transform: Mat3x4,
489 pub back_side: bool,
490 pub has_normals: bool,
495}
496
497impl Default for Relation {
498 fn default() -> Self {
499 Relation {
500 original_id: -1,
501 transform: Mat3x4::identity(),
502 back_side: false,
503 has_normals: false,
504 }
505 }
506}
507
508impl Relation {
509 pub fn get_normal_transform(&self) -> Mat3 {
513 let sign = if self.back_side { -1.0 } else { 1.0 };
514 self.transform.rotation().transpose().inverse() * sign
516 }
517
518 pub fn get_inverse_normal_transform(&self) -> Mat3 {
522 let sign = if self.back_side { -1.0 } else { 1.0 };
523 self.transform.rotation().transpose() * sign
525 }
526}
527
528#[derive(Clone, Debug, Default)]
530pub struct MeshRelationD {
531 pub original_id: i32,
533 pub mesh_id_transform: BTreeMap<i32, Relation>,
537 pub tri_ref: Vec<TriRef>,
538}
539
540impl MeshRelationD {
541 pub fn new() -> Self {
542 MeshRelationD {
543 original_id: -1,
544 mesh_id_transform: BTreeMap::new(),
545 tri_ref: Vec::new(),
546 }
547 }
548}
549
550#[inline]
556pub fn next_halfedge(current: i32) -> i32 {
557 let n = current + 1;
558 if n % 3 == 0 { n - 3 } else { n }
559}
560
561pub fn prev_halfedge(current: i32) -> i32 {
564 let base = current - (current % 3);
565 let pos = (current % 3 + 2) % 3;
566 base + pos
567}
568
569#[inline]
571pub fn next3(i: i32) -> i32 {
572 let n = i + 1;
573 if n == 3 { 0 } else { n }
574}
575
576#[cfg(test)]
578#[path = "types_tests.rs"]
579mod tests;