use core::marker::PhantomData;
use core::ptr::NonNull;
use crate::error::{Error, Result};
use crate::{Rect, Ring, RingRef};
macro_rules! impl_poly_methods {
($get_ptr:expr, $life:lifetime) => {
#[inline]
pub fn rect(&self) -> Rect {
let ptr = $get_ptr(self);
let r = unsafe { tg_geom_sys::tg_poly_rect(ptr) };
r.into()
}
#[inline]
pub fn num_holes(&self) -> usize {
let ptr = $get_ptr(self);
unsafe { tg_geom_sys::tg_poly_num_holes(ptr) as usize }
}
#[inline]
pub fn clockwise(&self) -> bool {
let ptr = $get_ptr(self);
unsafe { tg_geom_sys::tg_poly_clockwise(ptr) }
}
#[inline]
pub fn exterior(&self) -> RingRef<$life> {
let ptr = $get_ptr(self);
let ptr = unsafe { tg_geom_sys::tg_poly_exterior(ptr) };
unsafe { RingRef::from_raw(ptr).unwrap() }
}
#[inline]
pub fn hole_at(&self, index: usize) -> Option<RingRef<$life>> {
if index >= self.num_holes() {
None
} else {
let ptr = $get_ptr(self);
let ptr = unsafe { tg_geom_sys::tg_poly_hole_at(ptr, index as libc::c_int) };
unsafe { RingRef::from_raw(ptr) }
}
}
};
}
pub struct Poly {
ptr: NonNull<tg_geom_sys::tg_poly>,
}
impl Poly {
impl_poly_methods!(|s: &Self| s.ptr.as_ptr(), '_);
pub fn new(exterior: &Ring, holes: &[&Ring]) -> Result<Self> {
let hole_ptrs: Vec<*const tg_geom_sys::tg_ring> =
holes.iter().map(|r| r.as_ptr()).collect();
let ptr = unsafe {
tg_geom_sys::tg_poly_new(
exterior.as_ptr(),
hole_ptrs.as_ptr(),
holes.len() as libc::c_int,
)
};
NonNull::new(ptr)
.map(|ptr| Self { ptr })
.ok_or(Error::OutOfMemory)
}
pub fn new_simple(exterior: &Ring) -> Result<Self> {
let ptr = unsafe { tg_geom_sys::tg_poly_new(exterior.as_ptr(), core::ptr::null(), 0) };
NonNull::new(ptr)
.map(|ptr| Self { ptr })
.ok_or(Error::OutOfMemory)
}
pub unsafe fn from_raw(ptr: *mut tg_geom_sys::tg_poly) -> Option<Self> {
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[inline]
pub fn as_ptr(&self) -> *const tg_geom_sys::tg_poly {
self.ptr.as_ptr()
}
pub fn into_raw(self) -> *mut tg_geom_sys::tg_poly {
let ptr = self.ptr.as_ptr();
core::mem::forget(self);
ptr
}
pub fn copy(&self) -> Result<Self> {
let ptr = unsafe { tg_geom_sys::tg_poly_copy(self.ptr.as_ptr()) };
NonNull::new(ptr)
.map(|ptr| Self { ptr })
.ok_or(Error::CopyFailed)
}
pub fn clone_ref(&self) -> Result<Self> {
let ptr = unsafe { tg_geom_sys::tg_poly_clone(self.ptr.as_ptr()) };
NonNull::new(ptr)
.map(|ptr| Self { ptr })
.ok_or(Error::CopyFailed)
}
pub fn memsize(&self) -> usize {
unsafe { tg_geom_sys::tg_poly_memsize(self.ptr.as_ptr()) }
}
pub fn iter_holes(&self) -> impl Iterator<Item = RingRef<'_>> {
(0..self.num_holes()).map(move |i| self.hole_at(i).unwrap())
}
}
impl Drop for Poly {
fn drop(&mut self) {
unsafe { tg_geom_sys::tg_poly_free(self.ptr.as_ptr()) }
}
}
impl core::fmt::Debug for Poly {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Poly")
.field("num_holes", &self.num_holes())
.field("rect", &self.rect())
.field("clockwise", &self.clockwise())
.finish()
}
}
unsafe impl Send for Poly {}
unsafe impl Sync for Poly {}
#[derive(Clone, Copy)]
pub struct PolyRef<'a> {
ptr: *const tg_geom_sys::tg_poly,
_marker: PhantomData<&'a ()>,
}
unsafe impl Send for PolyRef<'_> {}
unsafe impl Sync for PolyRef<'_> {}
impl<'a> PolyRef<'a> {
impl_poly_methods!(|s: &Self| s.ptr, 'a);
#[inline]
pub unsafe fn from_raw(ptr: *const tg_geom_sys::tg_poly) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self {
ptr,
_marker: PhantomData,
})
}
}
#[inline]
pub fn as_ptr(&self) -> *const tg_geom_sys::tg_poly {
self.ptr
}
pub fn to_owned(&self) -> Result<Poly> {
let ptr = unsafe { tg_geom_sys::tg_poly_copy(self.ptr) };
NonNull::new(ptr)
.map(|ptr| Poly { ptr })
.ok_or(Error::CopyFailed)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Point;
fn p(x: f64, y: f64) -> Point {
Point::new(x, y)
}
fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
Rect::from_coords(min_x, min_y, max_x, max_y)
}
fn octagon() -> Vec<Point> {
vec![
p(3.0, 0.0),
p(7.0, 0.0),
p(10.0, 3.0),
p(10.0, 7.0),
p(7.0, 10.0),
p(3.0, 10.0),
p(0.0, 7.0),
p(0.0, 3.0),
p(3.0, 0.0),
]
}
fn small_hole() -> Vec<Point> {
vec![
p(4.0, 4.0),
p(6.0, 4.0),
p(6.0, 6.0),
p(4.0, 6.0),
p(4.0, 4.0),
]
}
fn rect_ring() -> Vec<Point> {
vec![
p(0.0, 0.0),
p(10.0, 0.0),
p(10.0, 10.0),
p(0.0, 10.0),
p(0.0, 0.0),
]
}
#[test]
fn test_poly_rect() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
assert_eq!(poly.rect(), r(0.0, 0.0, 10.0, 10.0));
}
#[test]
fn test_poly_rect_with_hole() {
let exterior = Ring::new(&octagon()).unwrap();
let hole = Ring::new(&small_hole()).unwrap();
let poly = Poly::new(&exterior, &[&hole]).unwrap();
assert_eq!(poly.rect(), r(0.0, 0.0, 10.0, 10.0));
}
#[test]
fn test_poly_exterior_holes() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
assert_eq!(poly.num_holes(), 0);
assert_eq!(poly.exterior().num_points(), 9);
}
#[test]
fn test_poly_with_hole() {
let exterior = Ring::new(&octagon()).unwrap();
let hole = Ring::new(&small_hole()).unwrap();
let poly = Poly::new(&exterior, &[&hole]).unwrap();
assert_eq!(poly.num_holes(), 1);
let hole_ref = poly.hole_at(0).unwrap();
assert_eq!(hole_ref.num_points(), 5);
}
#[test]
fn test_poly_hole_at_bounds() {
let exterior = Ring::new(&octagon()).unwrap();
let hole = Ring::new(&small_hole()).unwrap();
let poly = Poly::new(&exterior, &[&hole]).unwrap();
assert!(poly.hole_at(0).is_some());
assert!(poly.hole_at(1).is_none());
assert!(poly.hole_at(100).is_none());
}
#[test]
fn test_poly_no_holes() {
let exterior = Ring::new(&rect_ring()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
assert_eq!(poly.num_holes(), 0);
assert!(poly.hole_at(0).is_none());
}
#[test]
fn test_poly_clockwise() {
let ccw_exterior = Ring::new(&[
p(0.0, 0.0),
p(10.0, 0.0),
p(10.0, 10.0),
p(0.0, 10.0),
p(0.0, 0.0),
])
.unwrap();
let poly_ccw = Poly::new_simple(&ccw_exterior).unwrap();
assert!(!poly_ccw.clockwise());
let cw_exterior = Ring::new(&[
p(0.0, 0.0),
p(0.0, 10.0),
p(10.0, 10.0),
p(10.0, 0.0),
p(0.0, 0.0),
])
.unwrap();
let poly_cw = Poly::new_simple(&cw_exterior).unwrap();
assert!(poly_cw.clockwise());
}
#[test]
fn test_poly_copy() {
let exterior = Ring::new(&octagon()).unwrap();
let hole = Ring::new(&small_hole()).unwrap();
let poly = Poly::new(&exterior, &[&hole]).unwrap();
let copy = poly.copy().unwrap();
assert_eq!(poly.num_holes(), copy.num_holes());
assert_eq!(poly.rect(), copy.rect());
}
#[test]
fn test_poly_clone_ref() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
let cloned = poly.clone_ref().unwrap();
assert_eq!(poly.num_holes(), cloned.num_holes());
}
#[test]
fn test_poly_memsize() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
assert!(poly.memsize() > 0);
}
#[test]
fn test_poly_iter_holes() {
let exterior = Ring::new(&rect_ring()).unwrap();
let hole1 = Ring::new(&[
p(2.0, 2.0),
p(3.0, 2.0),
p(3.0, 3.0),
p(2.0, 3.0),
p(2.0, 2.0),
])
.unwrap();
let hole2 = Ring::new(&[
p(6.0, 6.0),
p(7.0, 6.0),
p(7.0, 7.0),
p(6.0, 7.0),
p(6.0, 6.0),
])
.unwrap();
let poly = Poly::new(&exterior, &[&hole1, &hole2]).unwrap();
let holes: Vec<RingRef<'_>> = poly.iter_holes().collect();
assert_eq!(holes.len(), 2);
}
#[test]
fn test_poly_debug() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
let debug_str = format!("{poly:?}");
assert!(debug_str.contains("Poly"));
assert!(debug_str.contains("num_holes"));
}
#[test]
fn test_poly_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Poly>();
assert_sync::<Poly>();
}
#[test]
fn test_poly_raw_pointer() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
let num_holes = poly.num_holes();
let ptr = poly.into_raw();
let recovered = unsafe { Poly::from_raw(ptr).unwrap() };
assert_eq!(recovered.num_holes(), num_holes);
}
#[test]
fn test_polyref_to_owned() {
let exterior = Ring::new(&octagon()).unwrap();
let poly = Poly::new_simple(&exterior).unwrap();
let poly_ref = unsafe { PolyRef::from_raw(poly.as_ptr()).unwrap() };
let owned = poly_ref.to_owned().unwrap();
assert_eq!(owned.num_holes(), 0);
}
}