#![doc(
html_logo_url = "https://raw.githubusercontent.com/virtualritz/libfive-rs/HEAD/libfive/libfive-logo.png"
)]
use core::{
ffi::c_void,
ops::{Add, Div, Mul, Neg, Rem, Sub},
ptr, result, slice,
};
use libfive_sys as sys;
use std::{ffi::CString, path::Path};
use derive_more::{Display, Error, From};
#[cfg(feature = "ahash")]
type HashMap<K, V> = ahash::AHashMap<K, V>;
#[cfg(not(feature = "ahash"))]
type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(feature = "stdlib")]
mod stdlib;
#[cfg(feature = "stdlib")]
pub use stdlib::*;
pub type Result<T> = result::Result<T, Error>;
#[derive(Clone, Copy, Debug, Display, Eq, Error, From, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum Error {
VariablesCouldNotBeUpdated,
VariableNotFound,
VariableAlreadyAdded,
FileWriteFailed,
FileReadFailed,
TreeIsNotConstant,
}
pub trait Point2 {
fn new(x: f32, y: f32) -> Self;
fn x(&self) -> f32;
fn y(&self) -> f32;
}
pub trait Point3 {
fn new(x: f32, y: f32, z: f32) -> Self;
fn x(&self) -> f32;
fn y(&self) -> f32;
fn z(&self) -> f32;
}
pub type Contour<T> = Vec<T>;
pub struct Bitmap(*mut sys::libfive_pixels);
impl Bitmap {
pub fn as_slice(&self) -> &[bool] {
let bitmap = unsafe { self.0.as_ref() }.unwrap();
unsafe {
slice::from_raw_parts(
bitmap.pixels,
(bitmap.width * bitmap.height) as _,
)
}
}
pub fn as_slice_mut(&mut self) -> &mut [bool] {
let bitmap = unsafe { self.0.as_mut() }.unwrap();
unsafe {
slice::from_raw_parts_mut(
bitmap.pixels,
(bitmap.width * bitmap.height) as _,
)
}
}
pub fn pixel(&self, x: u32, y: u32) -> bool {
assert!(x < self.width() && y < self.height());
self.as_slice()[(y * self.height() + x) as usize]
}
pub fn width(&self) -> u32 {
unsafe { self.0.as_ref() }.unwrap().width
}
pub fn height(&self) -> u32 {
unsafe { self.0.as_ref() }.unwrap().height
}
}
impl Drop for Bitmap {
fn drop(&mut self) {
unsafe { sys::libfive_pixels_delete(&mut self.0 as *mut _ as _) };
}
}
pub struct TriangleMesh<T: Point3> {
pub positions: Vec<T>,
pub triangles: Vec<[u32; 3]>,
}
pub struct FlatTriangleMesh {
pub positions: Vec<f32>,
pub triangles: Vec<u32>,
}
impl<T: Point3> From<TriangleMesh<T>> for FlatTriangleMesh {
fn from(mesh: TriangleMesh<T>) -> FlatTriangleMesh {
FlatTriangleMesh {
positions: mesh
.positions
.into_iter()
.flat_map(|point| [point.x(), point.y(), point.z()])
.collect(),
triangles: mesh.triangles.into_iter().flatten().collect(),
}
}
}
pub struct Variables {
map: HashMap<String, usize>,
variables: Vec<*const c_void>,
values: Vec<f32>,
sys_variables: sys::libfive_vars,
}
impl Default for Variables {
fn default() -> Self {
Variables::new()
}
}
impl Variables {
pub fn new() -> Self {
Self {
map: HashMap::new(),
variables: Vec::new(),
values: Vec::new(),
sys_variables: sys::libfive_vars {
vars: ptr::null(),
values: ptr::null_mut(),
size: 0,
},
}
}
pub fn add(&mut self, name: &str, value: f32) -> Result<Tree> {
let name = name.to_string();
if self.map.contains_key(&name) {
Err(Error::VariableAlreadyAdded)
} else {
let tree = unsafe { sys::libfive_tree_var() };
let id = unsafe { sys::libfive_tree_id(tree) };
self.map.insert(name, self.variables.len());
self.variables.push(id);
self.values.push(value);
self.sys_variables.vars = self.variables.as_ptr() as *const _ as _;
self.sys_variables.values = self.values.as_ptr() as *const _ as _;
self.sys_variables.size = self.variables.len().try_into().unwrap();
Ok(Tree(tree))
}
}
pub fn set(&mut self, name: &str, value: f32) -> Result<()> {
if let Some(&index) = self.map.get(name) {
self.values[index] = value;
Ok(())
} else {
Err(Error::VariableNotFound)
}
}
}
impl Drop for Variables {
fn drop(&mut self) {
unsafe {
sys::libfive_vars_delete(&mut self.sys_variables as *mut _ as _)
};
}
}
pub struct Evaluator(sys::libfive_evaluator);
impl Evaluator {
pub fn new(tree: &Tree, variables: &Variables) -> Self {
Self(unsafe {
sys::libfive_tree_evaluator(tree.0, variables.sys_variables)
})
}
pub fn update(&mut self, variables: &Variables) -> Result<()> {
if unsafe {
sys::libfive_evaluator_update_vars(self.0, variables.sys_variables)
} {
Err(Error::VariablesCouldNotBeUpdated)
} else {
Ok(())
}
}
pub fn write_stl(
&self,
path: impl AsRef<Path>,
region: &Region3,
) -> Result<()> {
let path = c_string_from_path(path);
if unsafe {
sys::libfive_evaluator_save_mesh(self.0, region.0, path.as_ptr())
} {
Ok(())
} else {
Err(Error::FileWriteFailed)
}
}
}
impl Drop for Evaluator {
fn drop(&mut self) {
unsafe { sys::libfive_evaluator_delete(self.0) };
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Region2(sys::libfive_region2);
impl Region2 {
pub fn new(x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> Self {
Self(sys::libfive_region2 {
X: sys::libfive_interval {
lower: x_min,
upper: x_max,
},
Y: sys::libfive_interval {
lower: y_min,
upper: y_max,
},
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Region3(sys::libfive_region3);
impl Region3 {
pub fn new(
x_min: f32,
x_max: f32,
y_min: f32,
y_max: f32,
z_min: f32,
z_max: f32,
) -> Self {
Self(sys::libfive_region3 {
X: sys::libfive_interval {
lower: x_min,
upper: x_max,
},
Y: sys::libfive_interval {
lower: y_min,
upper: y_max,
},
Z: sys::libfive_interval {
lower: z_min,
upper: z_max,
},
})
}
}
#[allow(dead_code)]
#[repr(i32)]
enum Op {
Invalid = 0,
Constant = 1,
VarX = 2,
VarY = 3,
VarZ = 4,
VarFree = 5,
ConstVar = 6,
Square = 7,
Sqrt = 8,
Neg = 9,
Sin = 10,
Cos = 11,
Tan = 12,
Asin = 13,
Acos = 14,
Atan = 15,
Exp = 16,
Abs = 28,
Log = 30,
Recip = 29,
Add = 17,
Mul = 18,
Min = 19,
Max = 20,
Sub = 21,
Div = 22,
Atan2 = 23,
Pow = 24,
NthRoot = 25,
Mod = 26,
NanFill = 27,
Compare = 31,
Oracle = 32,
}
macro_rules! fn_unary {
($func_name:ident, $op_code:ident) => {
#[inline]
pub fn $func_name(&self) -> Self {
Self(unsafe { sys::libfive_tree_unary(Op::$op_code as _, self.0) })
}
};
}
macro_rules! fn_binary {
($func_name:ident, $op_code:ident, $other:ident) => {
#[inline]
pub fn $func_name(self, $other: Self) -> Self {
Self(unsafe {
sys::libfive_tree_binary(Op::$op_code as _, self.0, $other.0)
})
}
};
}
macro_rules! op_binary {
($func_name:ident, $op_code:ident) => {
impl $op_code for Tree {
type Output = Tree;
#[inline]
fn $func_name(self, rhs: Tree) -> Self::Output {
self.$func_name(rhs)
}
}
};
}
#[derive(Eq, PartialEq)]
pub struct Tree(sys::libfive_tree);
pub type TreeFloat = Tree;
impl From<f32> for Tree {
fn from(constant: f32) -> Self {
Self(unsafe { sys::libfive_tree_const(constant) })
}
}
impl Tree {
#[inline]
pub fn x() -> Self {
Self(unsafe { sys::libfive_tree_x() })
}
#[inline]
pub fn y() -> Self {
Self(unsafe { sys::libfive_tree_y() })
}
#[inline]
pub fn z() -> Self {
Self(unsafe { sys::libfive_tree_z() })
}
}
impl Tree {
fn_unary!(square, Square);
fn_unary!(sqrt, Sqrt);
fn_unary!(neg, Neg);
fn_unary!(sin, Sin);
fn_unary!(cos, Cos);
fn_unary!(tan, Tan);
fn_unary!(asin, Asin);
fn_unary!(acos, Acos);
fn_unary!(atan, Atan);
fn_unary!(exp, Exp);
fn_unary!(abs, Abs);
fn_unary!(log, Log);
fn_unary!(recip, Recip);
fn_binary!(add, Add, rhs);
fn_binary!(mul, Mul, rhs);
fn_binary!(min, Min, rhs);
fn_binary!(max, Max, rhs);
fn_binary!(sub, Sub, rhs);
fn_binary!(div, Div, rhs);
fn_binary!(atan2, Atan2, other);
fn_binary!(pow, Pow, exp);
fn_binary!(nth_root, NthRoot, n);
fn_binary!(rem, Mod, rhs);
fn_binary!(nan_fill, NanFill, rhs);
fn_binary!(compare, Compare, rhs);
pub fn is_variable(&self) -> bool {
unsafe { sys::libfive_tree_is_var(self.0) }
}
pub fn as_f32(&self) -> Result<f32> {
let mut success = false;
let value = unsafe {
sys::libfive_tree_get_const(self.0, &mut success as *mut _)
};
if success {
Ok(value)
} else {
Err(Error::TreeIsNotConstant)
}
}
}
impl Tree {
#[inline]
pub fn to_bitmap(
&self,
region: &Region2,
z: f32,
resolution: f32,
) -> Bitmap {
Bitmap(unsafe {
sys::libfive_tree_render_pixels(self.0, region.0, z, resolution)
})
}
pub fn to_triangle_mesh<T: Point3>(
&self,
region: &Region3,
resolution: f32,
) -> Option<TriangleMesh<T>> {
match unsafe {
sys::libfive_tree_render_mesh(self.0, region.0, resolution).as_mut()
} {
Some(raw_mesh) => {
let mesh = TriangleMesh::<T> {
positions: (0..raw_mesh.vert_count)
.map(|index| {
let vertex =
&unsafe { *raw_mesh.verts.add(index as _) };
T::new(vertex.x, vertex.y, vertex.z)
})
.collect(),
triangles: (0..raw_mesh.tri_count)
.map(|index| {
let triangle =
&unsafe { *raw_mesh.tris.add(index as _) };
[triangle.a, triangle.b, triangle.c]
})
.collect(),
};
unsafe {
sys::libfive_mesh_delete(raw_mesh as *mut _ as _);
}
Some(mesh)
}
None => None,
}
}
pub fn to_contour_2d<T: Point2>(
&self,
region: Region2,
z: f32,
resolution: f32,
) -> Option<Vec<Contour<T>>> {
match unsafe {
sys::libfive_tree_render_slice(self.0, region.0, z, resolution)
.as_mut()
} {
Some(raw_contours) => {
let contours = (0..raw_contours.count)
.map(|index| {
let contour =
unsafe { raw_contours.cs.add(index as _).as_ref() }
.unwrap();
(0..contour.count)
.map(|index| {
let point = unsafe {
contour.pts.add(index as _).as_ref()
}
.unwrap();
T::new(point.x, point.y)
})
.collect()
})
.collect();
unsafe {
sys::libfive_contours_delete(raw_contours as *mut _ as _);
}
Some(contours)
}
None => None,
}
}
pub fn to_contour_3d<T: Point3>(
&self,
region: Region2,
z: f32,
resolution: f32,
) -> Option<Vec<Contour<T>>> {
let raw_contours = unsafe {
sys::libfive_tree_render_slice3(self.0, region.0, z, resolution)
.as_ref()
};
if let Some(raw_contours) = raw_contours {
let contours = (0..raw_contours.count)
.map(|index| {
let contour =
unsafe { raw_contours.cs.add(index as _).as_ref() }
.unwrap();
(0..contour.count)
.map(|index| {
let point =
unsafe { contour.pts.add(index as _).as_ref() }
.unwrap();
T::new(point.x, point.y, point.z)
})
.collect()
})
.collect();
unsafe {
sys::libfive_contours_delete(&raw_contours as *const _ as _);
}
Some(contours)
} else {
None
}
}
pub fn write_svg(
&self,
path: impl AsRef<Path>,
region: &Region2,
z: f32,
resolution: f32,
) {
let path = c_string_from_path(path);
unsafe {
sys::libfive_tree_save_slice(
self.0,
region.0,
z,
resolution,
path.as_ptr(),
);
}
}
pub fn write_stl(
&self,
path: impl AsRef<Path>,
region: &Region3,
resolution: f32,
) -> Result<()> {
let path = c_string_from_path(path);
println!("Foobar! {:?}", path);
if unsafe {
sys::libfive_tree_save_mesh(
self.0,
region.0,
resolution,
path.as_ptr(),
)
} {
Ok(())
} else {
Err(Error::FileWriteFailed)
}
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let path = c_string_from_path(path);
if unsafe { sys::libfive_tree_save(self.0, path.as_ptr()) } {
Ok(())
} else {
Err(Error::FileWriteFailed)
}
}
pub fn load(&self, path: impl AsRef<Path>) -> Result<Tree> {
let path = c_string_from_path(path);
match unsafe { sys::libfive_tree_load(path.as_ptr()).as_mut() } {
Some(tree) => Ok(Self(tree as _)),
None => Err(Error::FileReadFailed),
}
}
}
impl Drop for Tree {
fn drop(&mut self) {
unsafe { sys::libfive_tree_delete(self.0) };
}
}
op_binary!(add, Add);
op_binary!(div, Div);
op_binary!(mul, Mul);
op_binary!(rem, Rem);
op_binary!(sub, Sub);
impl Neg for Tree {
type Output = Tree;
fn neg(self) -> Self::Output {
Self(unsafe { sys::libfive_tree_unary(Op::Neg as _, self.0) })
}
}
fn c_string_from_path<P: AsRef<Path>>(path: P) -> CString {
CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap()
}
#[test]
fn test_2d() -> Result<()> {
let circle = Tree::x().square() + Tree::y().square() - 1.0.into();
circle.write_svg(
"circle.svg",
&Region2::new(-2.0, 2.0, -2.0, 2.0),
0.0,
10.0,
);
Ok(())
}
#[test]
#[cfg(feature = "stdlib")]
fn test_3d() -> Result<()> {
let f_rep_shape = Tree::sphere(1.0.into(), TreeVec3::default())
.difference_multi(vec![
Tree::sphere(0.6.into(), TreeVec3::default()),
Tree::cylinder_z(
0.6.into(),
2.0.into(),
TreeVec3::new(0.0, 0.0, -1.0),
),
Tree::cylinder_z(
0.6.into(),
2.0.into(),
TreeVec3::new(0.0, 0.0, -1.0),
)
.reflect_xz(),
Tree::cylinder_z(
0.6.into(),
2.0.into(),
TreeVec3::new(0.0, 0.0, -1.0),
)
.reflect_yz(),
]);
f_rep_shape.write_stl(
"f-rep-shape.stl",
&Region3::new(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0),
10.0,
)?;
Ok(())
}