packages feed

moonlight-triangulation-1.0.1.0: bindings/rust/src/lib.rs

use std::error::Error;
use std::ffi::{c_char, c_double, c_uint};
use std::fmt::{Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::rc::Rc;

const STATUS_OK: u32 = 0;
const ABI_VERSION: u32 = 1;

#[repr(C)]
struct NativeMesh {
    _private: [u8; 0],
}

#[repr(C)]
struct NativeObstruction {
    code: u32,
    coordinate_error: u32,
    input_index: u64,
    first_index: u64,
    second_index: u64,
    first_value: f64,
    second_value: f64,
    point_x: f64,
    point_y: f64,
    message: [c_char; 256],
}

impl Default for NativeObstruction {
    fn default() -> Self {
        Self {
            code: 0,
            coordinate_error: 0,
            input_index: u64::MAX,
            first_index: 0,
            second_index: 0,
            first_value: 0.0,
            second_value: 0.0,
            point_x: 0.0,
            point_y: 0.0,
            message: [0; 256],
        }
    }
}

#[link(name = "moonlight-triangulation-c")]
unsafe extern "C" {
    fn ml_abi_version() -> c_uint;
    fn ml_runtime_initialize() -> c_uint;
    fn ml_delaunay_f64(
        coordinates: *const c_double,
        point_count: usize,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_insert_many_f64(
        mesh: *const NativeMesh,
        coordinates: *const c_double,
        point_count: usize,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_union(
        left: *const NativeMesh,
        right: *const NativeMesh,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_intersection(
        left: *const NativeMesh,
        right: *const NativeMesh,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_difference(
        left: *const NativeMesh,
        right: *const NativeMesh,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_symmetric_difference(
        left: *const NativeMesh,
        right: *const NativeMesh,
        result: *mut *mut NativeMesh,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_vertex_count(
        mesh: *const NativeMesh,
        count: *mut usize,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_triangle_count(
        mesh: *const NativeMesh,
        count: *mut usize,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_copy_vertices_f64(
        mesh: *const NativeMesh,
        coordinates: *mut c_double,
        point_capacity: usize,
        points_written: *mut usize,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_copy_triangles_u32(
        mesh: *const NativeMesh,
        triangles: *mut u32,
        triangle_capacity: usize,
        triangles_written: *mut usize,
        obstruction: *mut NativeObstruction,
    ) -> c_uint;
    fn ml_mesh_free(mesh: *mut NativeMesh);
}

type BinaryMeshOperation = unsafe extern "C" fn(
    *const NativeMesh,
    *const NativeMesh,
    *mut *mut NativeMesh,
    *mut NativeObstruction,
) -> c_uint;

#[derive(Debug, Clone, PartialEq)]
pub struct MoonlightError {
    pub status: u32,
    pub code: u32,
    pub coordinate_error: u32,
    pub input_index: u64,
    pub first_index: u64,
    pub second_index: u64,
    pub first_value: f64,
    pub second_value: f64,
    pub point: [f64; 2],
    pub message: String,
}

impl Display for MoonlightError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}", self.message)
    }
}

impl Error for MoonlightError {}

pub struct Moonlight;

impl Moonlight {
    pub fn initialize() -> Result<Self, MoonlightError> {
        let status = unsafe { ml_runtime_initialize() };
        if status != STATUS_OK {
            return Err(synthetic_error(
                status,
                "Moonlight runtime initialization failed",
                0,
                0,
            ));
        }
        let version = unsafe { ml_abi_version() };
        if version != ABI_VERSION {
            return Err(synthetic_error(
                5,
                format!("unsupported Moonlight ABI version {version}"),
                u64::from(version),
                u64::from(ABI_VERSION),
            ));
        }
        Ok(Self)
    }

    pub fn delaunay(&self, points: &[[f64; 2]]) -> Result<Mesh, MoonlightError> {
        let coordinates = flatten_points(points);
        create_mesh(|output, obstruction| unsafe {
            ml_delaunay_f64(coordinates.as_ptr(), points.len(), output, obstruction)
        })
    }
}

pub struct Mesh {
    handle: NonNull<NativeMesh>,
    _thread_affinity: PhantomData<Rc<()>>,
}

impl Mesh {
    pub fn vertex_count(&self) -> Result<usize, MoonlightError> {
        self.count(ml_mesh_vertex_count)
    }

    pub fn triangle_count(&self) -> Result<usize, MoonlightError> {
        self.count(ml_mesh_triangle_count)
    }

    pub fn vertices(&self) -> Result<Vec<[f64; 2]>, MoonlightError> {
        let count = self.vertex_count()?;
        let mut coordinates = vec![0.0; count * 2];
        let mut written = 0;
        let mut obstruction = NativeObstruction::default();
        let status = unsafe {
            ml_mesh_copy_vertices_f64(
                self.handle.as_ptr(),
                coordinates.as_mut_ptr(),
                count,
                &mut written,
                &mut obstruction,
            )
        };
        status_result(status, obstruction)?;
        coordinates.truncate(written * 2);
        Ok(coordinates
            .chunks_exact(2)
            .map(|point| [point[0], point[1]])
            .collect())
    }

    pub fn triangles(&self) -> Result<Vec<[u32; 3]>, MoonlightError> {
        let count = self.triangle_count()?;
        let mut triangles = vec![0; count * 3];
        let mut written = 0;
        let mut obstruction = NativeObstruction::default();
        let status = unsafe {
            ml_mesh_copy_triangles_u32(
                self.handle.as_ptr(),
                triangles.as_mut_ptr(),
                count,
                &mut written,
                &mut obstruction,
            )
        };
        status_result(status, obstruction)?;
        triangles.truncate(written * 3);
        Ok(triangles
            .chunks_exact(3)
            .map(|triangle| [triangle[0], triangle[1], triangle[2]])
            .collect())
    }

    pub fn insert_many(&self, points: &[[f64; 2]]) -> Result<Self, MoonlightError> {
        let coordinates = flatten_points(points);
        create_mesh(|output, obstruction| unsafe {
            ml_mesh_insert_many_f64(
                self.handle.as_ptr(),
                coordinates.as_ptr(),
                points.len(),
                output,
                obstruction,
            )
        })
    }

    pub fn union(&self, other: &Self) -> Result<Self, MoonlightError> {
        self.binary(other, ml_mesh_union)
    }

    pub fn intersection(&self, other: &Self) -> Result<Self, MoonlightError> {
        self.binary(other, ml_mesh_intersection)
    }

    pub fn difference(&self, other: &Self) -> Result<Self, MoonlightError> {
        self.binary(other, ml_mesh_difference)
    }

    pub fn symmetric_difference(&self, other: &Self) -> Result<Self, MoonlightError> {
        self.binary(other, ml_mesh_symmetric_difference)
    }

    fn binary(&self, other: &Self, operation: BinaryMeshOperation) -> Result<Self, MoonlightError> {
        create_mesh(|output, obstruction| unsafe {
            operation(
                self.handle.as_ptr(),
                other.handle.as_ptr(),
                output,
                obstruction,
            )
        })
    }

    fn count(
        &self,
        operation: unsafe extern "C" fn(
            *const NativeMesh,
            *mut usize,
            *mut NativeObstruction,
        ) -> c_uint,
    ) -> Result<usize, MoonlightError> {
        let mut count = 0;
        let mut obstruction = NativeObstruction::default();
        let status = unsafe { operation(self.handle.as_ptr(), &mut count, &mut obstruction) };
        status_result(status, obstruction)?;
        Ok(count)
    }
}

impl Drop for Mesh {
    fn drop(&mut self) {
        unsafe { ml_mesh_free(self.handle.as_ptr()) }
    }
}

fn flatten_points(points: &[[f64; 2]]) -> Vec<f64> {
    points.iter().flat_map(|[x, y]| [*x, *y]).collect()
}

#[inline]
fn create_mesh(
    operation: impl FnOnce(*mut *mut NativeMesh, *mut NativeObstruction) -> u32,
) -> Result<Mesh, MoonlightError> {
    let mut output = std::ptr::null_mut();
    let mut obstruction = NativeObstruction::default();
    let status = operation(&mut output, &mut obstruction);
    status_result(status, obstruction)?;
    NonNull::new(output)
        .map(|handle| Mesh {
            handle,
            _thread_affinity: PhantomData,
        })
        .ok_or_else(success_without_handle_error)
}

fn status_result(status: u32, obstruction: NativeObstruction) -> Result<(), MoonlightError> {
    if status == STATUS_OK {
        Ok(())
    } else {
        Err(MoonlightError::from_native(status, obstruction))
    }
}

impl MoonlightError {
    fn from_native(status: u32, obstruction: NativeObstruction) -> Self {
        let message_end = obstruction
            .message
            .iter()
            .position(|character| *character == 0)
            .unwrap_or(obstruction.message.len());
        let message_bytes = obstruction.message[..message_end]
            .iter()
            .map(|character| *character as u8)
            .collect::<Vec<_>>();
        Self {
            status,
            code: obstruction.code,
            coordinate_error: obstruction.coordinate_error,
            input_index: obstruction.input_index,
            first_index: obstruction.first_index,
            second_index: obstruction.second_index,
            first_value: obstruction.first_value,
            second_value: obstruction.second_value,
            point: [obstruction.point_x, obstruction.point_y],
            message: String::from_utf8_lossy(&message_bytes).into_owned(),
        }
    }
}

fn success_without_handle_error() -> MoonlightError {
    synthetic_error(5, "Moonlight returned success without a mesh handle", 0, 0)
}

fn synthetic_error(
    status: u32,
    message: impl Into<String>,
    first_index: u64,
    second_index: u64,
) -> MoonlightError {
    MoonlightError {
        status,
        code: 103,
        coordinate_error: 0,
        input_index: u64::MAX,
        first_index,
        second_index,
        first_value: 0.0,
        second_value: 0.0,
        point: [0.0, 0.0],
        message: message.into(),
    }
}