use crate::constants::{handle_status, LIGHT_COLORS, TRITET_TO_TRIANGLE};
use crate::conversion::to_i32;
use crate::InputDataTriMesh;
use crate::StrError;
use plotpy::{Canvas, Curve, Plot, PolyCode, Text};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Write;
use std::fs::{self, File};
use std::io::Write as IoWrite;
use std::path::Path;
#[repr(C)]
pub(crate) struct ExtTrigen {
data: [u8; 0],
marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
extern "C" {
fn tri_new_trigen(npoint: i32, nsegment: i32, nregion: i32, nhole: i32) -> *mut ExtTrigen;
fn tri_drop_trigen(trigen: *mut ExtTrigen);
fn tri_set_point(trigen: *mut ExtTrigen, index: i32, marker: i32, x: f64, y: f64) -> i32;
fn tri_set_segment(trigen: *mut ExtTrigen, index: i32, marker: i32, a: i32, b: i32) -> i32;
fn tri_set_region(trigen: *mut ExtTrigen, index: i32, marker: i32, x: f64, y: f64, max_area: f64) -> i32;
fn tri_set_hole(trigen: *mut ExtTrigen, index: i32, x: f64, y: f64) -> i32;
fn tri_run_delaunay(trigen: *mut ExtTrigen, verbose: i32) -> i32;
fn tri_run_voronoi(trigen: *mut ExtTrigen, verbose: i32) -> i32;
fn tri_run_triangulate(
trigen: *mut ExtTrigen,
verbose: i32,
quadratic: i32,
allow_new_points_on_bry: i32,
global_max_area: f64,
global_min_angle: f64,
) -> i32;
fn tri_out_npoint(trigen: *mut ExtTrigen) -> i32;
fn tri_out_nsegment(trigen: *mut ExtTrigen) -> i32;
fn tri_out_ncell(trigen: *mut ExtTrigen) -> i32;
fn tri_out_cell_npoint(trigen: *mut ExtTrigen) -> i32;
fn tri_out_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64;
fn tri_out_point_marker(trigen: *mut ExtTrigen, index: i32) -> i32;
fn tri_out_segment_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32;
fn tri_out_segment_marker(trigen: *mut ExtTrigen, index: i32) -> i32;
fn tri_out_cell_point(trigen: *mut ExtTrigen, index: i32, corner: i32) -> i32;
fn tri_out_cell_marker(trigen: *mut ExtTrigen, index: i32) -> i32;
fn tri_out_voronoi_npoint(trigen: *mut ExtTrigen) -> i32;
fn tri_out_voronoi_point(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64;
fn tri_out_voronoi_nedge(trigen: *mut ExtTrigen) -> i32;
fn tri_out_voronoi_edge_point(trigen: *mut ExtTrigen, index: i32, side: i32) -> i32;
fn tri_out_voronoi_edge_point_b_direction(trigen: *mut ExtTrigen, index: i32, dim: i32) -> f64;
}
#[derive(Clone, Debug)]
pub enum VoronoiEdgePoint {
Index(usize),
Direction(f64, f64),
}
pub struct Trigen {
ext_trigen: *mut ExtTrigen, npoint: usize, nsegment: Option<usize>, nregion: Option<usize>, nhole: Option<usize>, all_points_set: bool, all_segments_set: bool, all_regions_set: bool, all_holes_set: bool, }
impl Drop for Trigen {
fn drop(&mut self) {
unsafe {
tri_drop_trigen(self.ext_trigen);
}
}
}
impl Trigen {
pub fn from_input_data(data: &InputDataTriMesh) -> Result<Self, StrError> {
let npoint = data.points.len();
let nsegment = data.segments.len();
let nregion = data.regions.len();
let nhole = data.holes.len();
let mut trigen = Trigen::new(npoint, Some(nsegment), Some(nregion), Some(nhole))?;
for (i, p) in data.points.iter().enumerate() {
trigen.set_point(i, p.0, p.1, p.2)?;
}
for (i, s) in data.segments.iter().enumerate() {
trigen.set_segment(i, s.0, s.1, s.2)?;
}
for (i, r) in data.regions.iter().enumerate() {
trigen.set_region(i, r.0, r.1, r.2, r.3)?;
}
for (i, h) in data.holes.iter().enumerate() {
trigen.set_hole(i, h.0, h.1)?;
}
Ok(trigen)
}
pub fn new(
npoint: usize,
nsegment: Option<usize>,
nregion: Option<usize>,
nhole: Option<usize>,
) -> Result<Self, StrError> {
if npoint < 3 {
return Err("npoint must be ≥ 3");
}
if let Some(ns) = nsegment {
if ns < 3 {
return Err("nsegment must be ≥ 3");
}
}
let npoint_i32: i32 = to_i32(npoint);
let nsegment_i32: i32 = match nsegment {
Some(v) => to_i32(v),
None => 0,
};
let nregion_i32: i32 = match nregion {
Some(v) => to_i32(v),
None => 0,
};
let nhole_i32: i32 = match nhole {
Some(v) => to_i32(v),
None => 0,
};
unsafe {
let ext_triangle = tri_new_trigen(npoint_i32, nsegment_i32, nregion_i32, nhole_i32);
if ext_triangle.is_null() {
return Err("INTERNAL ERROR: cannot allocate ExtTriangle");
}
Ok(Trigen {
ext_trigen: ext_triangle,
npoint,
nsegment,
nregion,
nhole,
all_points_set: false,
all_segments_set: false,
all_regions_set: false,
all_holes_set: false,
})
}
}
pub fn set_point(&mut self, index: usize, marker: i32, x: f64, y: f64) -> Result<&mut Self, StrError> {
unsafe {
let status = tri_set_point(self.ext_trigen, to_i32(index), marker, x, y);
handle_status(status)?;
}
if index == self.npoint - 1 {
self.all_points_set = true;
} else {
self.all_points_set = false;
}
Ok(self)
}
pub fn set_segment(&mut self, index: usize, marker: i32, a: usize, b: usize) -> Result<&mut Self, StrError> {
let nsegment = match self.nsegment {
Some(n) => n,
None => return Err("cannot set segment because the number of segments is None"),
};
unsafe {
let status = tri_set_segment(self.ext_trigen, to_i32(index), marker, to_i32(a), to_i32(b));
handle_status(status)?;
}
if index == nsegment - 1 {
self.all_segments_set = true;
} else {
self.all_segments_set = false;
}
Ok(self)
}
pub fn set_region(
&mut self,
index: usize,
marker: i32,
x: f64,
y: f64,
max_area: Option<f64>,
) -> Result<&mut Self, StrError> {
let nregion = match self.nregion {
Some(n) => n,
None => return Err("cannot set region because the number of regions is None"),
};
let area_constraint = match max_area {
Some(v) => v,
None => -1.0,
};
unsafe {
let status = tri_set_region(self.ext_trigen, to_i32(index), marker, x, y, area_constraint);
handle_status(status)?;
}
if index == nregion - 1 {
self.all_regions_set = true;
} else {
self.all_regions_set = false;
}
Ok(self)
}
pub fn set_hole(&mut self, index: usize, x: f64, y: f64) -> Result<&mut Self, StrError> {
let nhole = match self.nhole {
Some(n) => n,
None => return Err("cannot set hole because the number of holes is None"),
};
unsafe {
let status = tri_set_hole(self.ext_trigen, to_i32(index), x, y);
handle_status(status)?;
}
if index == nhole - 1 {
self.all_holes_set = true;
} else {
self.all_holes_set = false;
}
Ok(self)
}
pub fn generate_delaunay(&self, verbose: bool) -> Result<(), StrError> {
if !self.all_points_set {
return Err("cannot generate Delaunay triangulation because not all points are set");
}
unsafe {
let status = tri_run_delaunay(self.ext_trigen, if verbose { 1 } else { 0 });
handle_status(status)?;
}
Ok(())
}
pub fn generate_voronoi(&self, verbose: bool) -> Result<(), StrError> {
if !self.all_points_set {
return Err("cannot generate Voronoi tessellation because not all points are set");
}
unsafe {
let status = tri_run_voronoi(self.ext_trigen, if verbose { 1 } else { 0 });
handle_status(status)?;
}
Ok(())
}
pub fn generate_mesh(
&self,
verbose: bool,
quadratic: bool,
allow_new_points_on_bry: bool,
global_max_area: Option<f64>,
global_min_angle: Option<f64>,
) -> Result<(), StrError> {
if !self.all_points_set {
return Err("cannot generate mesh of triangles because not all points are set");
}
if !self.all_segments_set {
return Err("cannot generate mesh of triangles because not all segments are set");
}
let max_area = match global_max_area {
Some(v) => v,
None => 0.0,
};
let min_angle = match global_min_angle {
Some(v) => v,
None => 0.0,
};
unsafe {
let status = tri_run_triangulate(
self.ext_trigen,
if verbose { 1 } else { 0 },
if quadratic { 1 } else { 0 },
if allow_new_points_on_bry { 1 } else { 0 },
max_area,
min_angle,
);
handle_status(status)?;
}
Ok(())
}
pub fn out_npoint(&self) -> usize {
unsafe { tri_out_npoint(self.ext_trigen) as usize }
}
pub fn out_nsegment(&self) -> usize {
unsafe { tri_out_nsegment(self.ext_trigen) as usize }
}
pub fn out_ncell(&self) -> usize {
unsafe { tri_out_ncell(self.ext_trigen) as usize }
}
pub fn out_cell_npoint(&self) -> usize {
unsafe { tri_out_cell_npoint(self.ext_trigen) as usize }
}
pub fn out_point(&self, index: usize, dim: usize) -> f64 {
unsafe { tri_out_point(self.ext_trigen, to_i32(index), to_i32(dim)) }
}
pub fn out_point_marker(&self, index: usize) -> i32 {
unsafe { tri_out_point_marker(self.ext_trigen, to_i32(index)) }
}
pub fn out_segment_point(&self, index: usize, side: usize) -> usize {
unsafe { tri_out_segment_point(self.ext_trigen, to_i32(index), to_i32(side)) as usize }
}
pub fn out_segment_marker(&self, index: usize) -> i32 {
unsafe { tri_out_segment_marker(self.ext_trigen, to_i32(index)) }
}
pub fn out_cell_point(&self, index: usize, m: usize) -> usize {
unsafe {
let corner = TRITET_TO_TRIANGLE[m];
tri_out_cell_point(self.ext_trigen, to_i32(index), to_i32(corner)) as usize
}
}
pub fn out_cell_marker(&self, index: usize) -> i32 {
unsafe { tri_out_cell_marker(self.ext_trigen, to_i32(index)) }
}
pub fn out_voronoi_npoint(&self) -> usize {
unsafe { tri_out_voronoi_npoint(self.ext_trigen) as usize }
}
pub fn out_voronoi_point(&self, index: usize, dim: usize) -> f64 {
unsafe { tri_out_voronoi_point(self.ext_trigen, to_i32(index), to_i32(dim)) }
}
pub fn out_voronoi_nedge(&self) -> usize {
unsafe { tri_out_voronoi_nedge(self.ext_trigen) as usize }
}
pub fn out_voronoi_edge_point_a(&self, index: usize) -> usize {
unsafe { tri_out_voronoi_edge_point(self.ext_trigen, to_i32(index), 0) as usize }
}
pub fn out_voronoi_edge_point_b(&self, index: usize) -> VoronoiEdgePoint {
unsafe {
let index_i32 = to_i32(index);
let id = tri_out_voronoi_edge_point(self.ext_trigen, index_i32, 1);
if id == -1 {
let x = tri_out_voronoi_edge_point_b_direction(self.ext_trigen, index_i32, 0);
let y = tri_out_voronoi_edge_point_b_direction(self.ext_trigen, index_i32, 1);
VoronoiEdgePoint::Direction(x, y)
} else {
VoronoiEdgePoint::Index(id as usize)
}
}
}
pub fn draw_triangles(
&self,
plot: &mut Plot,
set_range: bool,
with_point_ids: bool,
with_triangle_ids: bool,
with_markers: bool,
fontsize_point_ids: Option<f64>,
fontsize_triangle_ids: Option<f64>,
fontsize_markers: Option<f64>,
) {
let n_triangle = self.out_ncell();
if n_triangle < 1 {
return;
}
let mut canvas = Canvas::new();
let mut point_ids = Text::new();
let mut triangle_ids = Text::new();
let mut markers = Text::new();
if with_point_ids {
point_ids
.set_color("red")
.set_align_horizontal("center")
.set_align_vertical("center")
.set_bbox(true)
.set_bbox_facecolor("white")
.set_bbox_alpha(0.8)
.set_bbox_style("circle");
if let Some(fsz) = fontsize_point_ids {
point_ids.set_fontsize(fsz);
}
}
if with_triangle_ids {
triangle_ids
.set_color("blue")
.set_align_horizontal("center")
.set_align_vertical("center");
if let Some(fsz) = fontsize_triangle_ids {
triangle_ids.set_fontsize(fsz);
}
}
if with_markers {
markers
.set_color("black")
.set_align_horizontal("center")
.set_align_vertical("center");
if let Some(fsz) = fontsize_markers {
markers.set_fontsize(fsz);
}
}
canvas.set_edge_color("black");
let mut x = vec![0.0; 2];
let mut xmid = vec![0.0; 2];
let mut xatt = vec![0.0; 2];
let mut min = vec![f64::MAX; 2];
let mut max = vec![f64::MIN; 2];
let mut colors: HashMap<i32, &'static str> = HashMap::new();
let mut index_color = 0;
let clr = LIGHT_COLORS;
for tri in 0..n_triangle {
let marker = self.out_cell_marker(tri);
let color = match colors.get(&marker) {
Some(c) => c,
None => {
let c = clr[index_color % clr.len()];
colors.insert(marker, c);
index_color += 1;
c
}
};
canvas.set_face_color(color);
canvas.polycurve_begin();
for dim in 0..2 {
xmid[dim] = 0.0;
}
for m in 0..3 {
let p = self.out_cell_point(tri, m);
for dim in 0..2 {
x[dim] = self.out_point(p, dim);
min[dim] = f64::min(min[dim], x[dim]);
max[dim] = f64::max(max[dim], x[dim]);
xmid[dim] += x[dim] / 3.0;
}
if m == 0 {
canvas.polycurve_add(x[0], x[1], PolyCode::MoveTo);
} else {
canvas.polycurve_add(x[0], x[1], PolyCode::LineTo);
}
}
canvas.polycurve_end(true);
if with_triangle_ids {
triangle_ids.draw(xmid[0], xmid[1], format!("{}", tri).as_str());
}
if with_markers {
let p = self.out_cell_point(tri, 0);
for dim in 0..2 {
x[dim] = self.out_point(p, dim);
xatt[dim] = (x[dim] + xmid[dim]) / 2.0;
}
markers.draw(xatt[0], xatt[1], format!("[{}]", marker).as_str());
}
}
if with_point_ids {
for p in 0..self.out_npoint() {
let x_val = self.out_point(p, 0);
let y_val = self.out_point(p, 1);
point_ids.draw(x_val, y_val, format!("{}", p).as_str());
}
}
plot.add(&canvas);
if with_triangle_ids {
plot.add(&triangle_ids);
}
if with_point_ids {
plot.add(&point_ids);
}
if with_markers {
plot.add(&markers);
}
if set_range {
plot.set_range(min[0], max[0], min[1], max[1]);
}
}
pub fn draw_voronoi(&self, plot: &mut Plot) {
if self.out_voronoi_npoint() < 1 || self.out_voronoi_nedge() < 1 {
return;
}
let mut x = vec![0.0; 2];
let mut min = vec![f64::MAX; 2];
let mut max = vec![f64::MIN; 2];
let mut markers = Curve::new();
markers
.set_marker_color("gold")
.set_marker_line_color("gold")
.set_marker_style("o")
.set_stop_clip(true);
for p in 0..self.out_npoint() {
for dim in 0..2 {
x[dim] = self.out_point(p, dim);
min[dim] = f64::min(min[dim], x[dim]);
max[dim] = f64::max(max[dim], x[dim]);
}
markers.draw(&[x[0]], &[x[1]]);
}
for q in 0..self.out_voronoi_npoint() {
for dim in 0..2 {
x[dim] = self.out_voronoi_point(q, dim);
min[dim] = f64::min(min[dim], x[dim]);
max[dim] = f64::max(max[dim], x[dim]);
}
}
let mut gap = vec![0.0; 2];
for dim in 0..2 {
gap[dim] = 0.05 * (max[dim] - min[dim]);
gap[dim] = 0.05 * (max[dim] - min[dim]);
min[dim] -= gap[dim];
max[dim] += gap[dim];
}
let mut canvas = Canvas::new();
canvas.polycurve_begin();
for e in 0..self.out_voronoi_nedge() {
let a = self.out_voronoi_edge_point_a(e);
let xa = self.out_voronoi_point(a, 0);
let ya = self.out_voronoi_point(a, 1);
let b_or_direction = self.out_voronoi_edge_point_b(e);
match b_or_direction {
VoronoiEdgePoint::Index(b) => {
let xb = self.out_voronoi_point(b, 0);
let yb = self.out_voronoi_point(b, 1);
canvas.polycurve_add(xa, ya, PolyCode::MoveTo);
canvas.polycurve_add(xb, yb, PolyCode::LineTo);
}
VoronoiEdgePoint::Direction(dx, dy) => {
let mx = if dx > 0.0 {
(max[0] - xa) / dx
} else if dx < 0.0 {
(min[0] - xa) / dx
} else {
0.0
};
let my = if dy > 0.0 {
(max[1] - ya) / dy
} else if dy < 0.0 {
(min[1] - ya) / dy
} else {
0.0
};
let m = if mx < my { mx } else { my };
if m > 0.0 {
let xb = xa + m * dx;
let yb = ya + m * dy;
canvas.polycurve_add(xa, ya, PolyCode::MoveTo);
canvas.polycurve_add(xb, yb, PolyCode::LineTo);
} else {
canvas.polycurve_add(xa, ya, PolyCode::MoveTo);
if dx == 0.0 {
canvas.polycurve_add(xa, ya + dy * gap[1], PolyCode::LineTo);
} else if dy == 0.0 {
canvas.polycurve_add(xa + dx * gap[0], ya, PolyCode::LineTo);
}
}
}
}
}
canvas.polycurve_end(false);
plot.set_range(min[0], max[0], min[1], max[1]);
plot.add(&canvas).add(&markers);
}
pub fn write_msh<P>(&self, full_path: &P) -> Result<(), StrError>
where
P: AsRef<OsStr> + ?Sized,
{
let npoint = self.out_npoint();
let ncell = self.out_ncell();
if npoint < 1 || ncell < 1 {
return Err("mesh is empty: cannot write msh file");
}
let mut nmarked_edge: usize = 0;
for i in 0..self.out_nsegment() {
let marker = self.out_segment_marker(i);
if marker != 0 {
nmarked_edge += 1;
}
}
let mut f = String::new();
write!(f, "# header\n").unwrap();
write!(f, "# ndim npoint ncell nmarked_edge nmarked_face\n").unwrap();
write!(f, "2 {} {} {} 0\n", npoint, ncell, nmarked_edge).unwrap();
write!(f, "\n# points\n").unwrap();
write!(f, "# id marker x y\n").unwrap();
for i in 0..npoint {
let a = self.out_point_marker(i);
let x = self.out_point(i, 0);
let y = self.out_point(i, 1);
write!(f, "{} {} {:?} {:?}\n", i, a, x, y).unwrap();
}
write!(f, "\n# cells\n").unwrap();
write!(f, "# id marker kind points\n").unwrap();
let mut b = String::new();
for i in 0..ncell {
let a = self.out_cell_marker(i);
let k = if self.out_cell_npoint() == 6 { "tri6" } else { "tri3" };
b.clear();
for m in 0..self.out_cell_npoint() {
write!(b, " {}", self.out_cell_point(i, m)).unwrap();
}
write!(f, "{} {} {}{}\n", i, a, k, b).unwrap();
}
if nmarked_edge > 0 {
write!(f, "\n# marked edges\n").unwrap();
write!(f, "# marker p1 p2\n").unwrap();
for i in 0..self.out_nsegment() {
let marker = self.out_segment_marker(i);
if marker != 0 {
let a = self.out_segment_point(i, 0);
let b = self.out_segment_point(i, 1);
write!(f, "{} {} {}\n", marker, a, b).unwrap();
}
}
}
let path = Path::new(full_path);
if let Some(p) = path.parent() {
fs::create_dir_all(p).map_err(|_| "cannot create directory")?;
}
let mut file = File::create(path).map_err(|_| "cannot create file")?;
file.write_all(f.as_bytes()).map_err(|_| "cannot write file")?;
file.sync_all().map_err(|_| "cannot sync file")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Trigen;
use crate::InputDataTriMesh;
use crate::{StrError, VoronoiEdgePoint};
use plotpy::Plot;
use std::fs;
const SAVE_FIGURE: bool = false;
#[test]
fn derive_works() {
let option = VoronoiEdgePoint::Index(0);
let cloned = option.clone();
assert_eq!(format!("{:?}", option), "Index(0)");
assert_eq!(format!("{:?}", cloned), "Index(0)");
}
#[test]
fn new_captures_some_errors() {
assert_eq!(Trigen::new(2, None, None, None).err(), Some("npoint must be ≥ 3"));
assert_eq!(Trigen::new(3, Some(2), None, None).err(), Some("nsegment must be ≥ 3"));
}
#[test]
fn new_works() -> Result<(), StrError> {
let trigen = Trigen::new(3, Some(3), None, None)?;
assert_eq!(trigen.ext_trigen.is_null(), false);
assert_eq!(trigen.npoint, 3);
assert_eq!(trigen.nsegment, Some(3));
assert_eq!(trigen.nregion, None);
assert_eq!(trigen.nhole, None);
assert_eq!(trigen.all_points_set, false);
assert_eq!(trigen.all_segments_set, false);
assert_eq!(trigen.all_regions_set, false);
assert_eq!(trigen.all_holes_set, false);
Ok(())
}
#[test]
fn set_point_captures_some_errors() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, None, None, None)?;
assert_eq!(
trigen.set_point(4, 0, 0.0, 0.0).err(),
Some("index of point is out of bounds")
);
Ok(())
}
#[test]
fn set_segment_captures_some_errors() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, None, None, None)?;
assert_eq!(
trigen.set_segment(0, -10, 0, 1).err(),
Some("cannot set segment because the number of segments is None")
);
let mut trigen = Trigen::new(3, Some(3), None, None)?;
assert_eq!(
trigen.set_segment(4, -10, 0, 1).err(),
Some("index of segment is out of bounds")
);
assert_eq!(
trigen.set_segment(0, -10, 0, 4).err(),
Some("id of segment point is out of bounds")
);
Ok(())
}
#[test]
fn set_region_captures_some_errors() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, None, None, None)?;
assert_eq!(
trigen.set_region(0, 1, 0.33, 0.33, Some(0.1)).err(),
Some("cannot set region because the number of regions is None")
);
let mut trigen = Trigen::new(3, Some(3), Some(1), None)?;
assert_eq!(
trigen.set_region(1, 1, 0.33, 0.33, Some(0.1)).err(),
Some("index of region is out of bounds")
);
Ok(())
}
#[test]
fn set_hole_captures_some_errors() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, None, None, None)?;
assert_eq!(
trigen.set_hole(0, 0.33, 0.33).err(),
Some("cannot set hole because the number of holes is None")
);
let mut trigen = Trigen::new(3, Some(3), Some(1), Some(1))?;
assert_eq!(
trigen.set_hole(1, 0.33, 0.33).err(),
Some("index of hole is out of bounds")
);
Ok(())
}
#[test]
fn generate_methods_capture_some_errors() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, Some(3), None, None)?;
assert_eq!(
trigen.generate_delaunay(false).err(),
Some("cannot generate Delaunay triangulation because not all points are set")
);
assert_eq!(
trigen.generate_voronoi(false).err(),
Some("cannot generate Voronoi tessellation because not all points are set")
);
assert_eq!(
trigen.generate_mesh(false, false, false, None, None).err(),
Some("cannot generate mesh of triangles because not all points are set")
);
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 0.0, 1.0)?;
assert_eq!(
trigen.generate_mesh(false, false, false, None, None).err(),
Some("cannot generate mesh of triangles because not all segments are set")
);
Ok(())
}
#[test]
fn delaunay_1_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, None, None, None)?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 0.0, 1.0)?;
trigen.generate_delaunay(false)?;
assert_eq!(trigen.out_npoint(), 3);
assert_eq!(trigen.out_ncell(), 1);
assert_eq!(trigen.out_cell_npoint(), 3);
assert_eq!(trigen.out_point(0, 0), 0.0);
assert_eq!(trigen.out_point(0, 1), 0.0);
assert_eq!(trigen.out_point(1, 0), 1.0);
assert_eq!(trigen.out_point(1, 1), 0.0);
assert_eq!(trigen.out_point(2, 0), 0.0);
assert_eq!(trigen.out_point(2, 1), 1.0);
assert_eq!(trigen.out_cell_point(0, 0), 0);
assert_eq!(trigen.out_cell_point(0, 1), 1);
assert_eq!(trigen.out_cell_point(0, 2), 2);
assert_eq!(trigen.out_voronoi_npoint(), 0);
assert_eq!(trigen.out_voronoi_nedge(), 0);
Ok(())
}
#[test]
fn voronoi_1_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(5, None, None, None)?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 0.0, 1.0)?
.set_point(3, 0, 1.0, 1.0)?
.set_point(4, 0, 0.5, 0.5)?;
trigen.generate_voronoi(false)?;
if SAVE_FIGURE {
let mut plot = Plot::new();
trigen.draw_voronoi(&mut plot);
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/voronoi_1_works.svg")?;
}
for i in 0..trigen.out_voronoi_npoint() {
println!(
"{}: {:?}, {:?}",
i,
trigen.out_voronoi_point(i, 0),
trigen.out_voronoi_point(i, 1)
);
}
for e in 0..trigen.out_voronoi_nedge() {
println!(
"{}: {:?} => {:?}",
e,
trigen.out_voronoi_edge_point_a(e),
trigen.out_voronoi_edge_point_b(e)
);
}
assert_eq!(trigen.out_npoint(), 5);
assert_eq!(trigen.out_ncell(), 4);
assert_eq!(trigen.out_cell_npoint(), 3);
assert_eq!(trigen.out_point(0, 0), 0.0);
assert_eq!(trigen.out_point(0, 1), 0.0);
assert_eq!(trigen.out_point(1, 0), 1.0);
assert_eq!(trigen.out_point(1, 1), 0.0);
assert_eq!(trigen.out_point(2, 0), 0.0);
assert_eq!(trigen.out_point(2, 1), 1.0);
assert_eq!(trigen.out_point(3, 0), 1.0);
assert_eq!(trigen.out_point(3, 1), 1.0);
assert_eq!(trigen.out_point(4, 0), 0.5);
assert_eq!(trigen.out_point(4, 1), 0.5);
assert_eq!(trigen.out_voronoi_npoint(), 4);
assert_eq!(trigen.out_voronoi_point(0, 0), 0.0);
assert_eq!(trigen.out_voronoi_point(0, 1), 0.5);
assert_eq!(trigen.out_voronoi_point(1, 0), 1.0);
assert_eq!(trigen.out_voronoi_point(1, 1), 0.5);
assert_eq!(trigen.out_voronoi_point(2, 0), 0.5);
assert_eq!(trigen.out_voronoi_point(2, 1), 0.0);
assert_eq!(trigen.out_voronoi_point(3, 0), 0.5);
assert_eq!(trigen.out_voronoi_point(3, 1), 1.0);
assert_eq!(trigen.out_voronoi_nedge(), 8);
assert_eq!(trigen.out_voronoi_edge_point_a(0), 0);
assert_eq!(
format!("{:?}", trigen.out_voronoi_edge_point_b(0)),
"Direction(-1.0, 0.0)"
);
assert_eq!(trigen.out_voronoi_edge_point_a(1), 0);
assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(1)), "Index(2)");
assert_eq!(trigen.out_voronoi_edge_point_a(2), 0);
assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(2)), "Index(3)");
assert_eq!(trigen.out_voronoi_edge_point_a(3), 1);
assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(3)), "Index(2)");
assert_eq!(trigen.out_voronoi_edge_point_a(4), 1);
assert_eq!(
format!("{:?}", trigen.out_voronoi_edge_point_b(4)),
"Direction(1.0, 0.0)"
);
assert_eq!(trigen.out_voronoi_edge_point_a(5), 1);
assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(5)), "Index(3)");
assert_eq!(trigen.out_voronoi_edge_point_a(6), 2);
assert_eq!(
format!("{:?}", trigen.out_voronoi_edge_point_b(6)),
"Direction(0.0, -1.0)"
);
assert_eq!(trigen.out_voronoi_edge_point_a(7), 3);
assert_eq!(
format!("{:?}", trigen.out_voronoi_edge_point_b(7)),
"Direction(0.0, 1.0)"
);
Ok(())
}
#[test]
fn mesh_1_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, Some(3), None, None)?;
trigen
.set_point(0, -100, 0.0, 0.0)?
.set_point(1, -200, 1.0, 0.0)?
.set_point(2, -300, 0.0, 1.0)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, -20, 1, 2)?
.set_segment(2, -30, 2, 0)?;
trigen.generate_mesh(false, false, false, None, None)?;
assert_eq!(trigen.out_npoint(), 3);
assert_eq!(trigen.out_nsegment(), 3);
assert_eq!(trigen.out_ncell(), 1);
assert_eq!(trigen.out_cell_npoint(), 3);
assert_eq!(trigen.out_point(0, 0), 0.0);
assert_eq!(trigen.out_point(0, 1), 0.0);
assert_eq!(trigen.out_point(1, 0), 1.0);
assert_eq!(trigen.out_point(1, 1), 0.0);
assert_eq!(trigen.out_point(2, 0), 0.0);
assert_eq!(trigen.out_point(2, 1), 1.0);
assert_eq!(trigen.out_point_marker(0), -100);
assert_eq!(trigen.out_point_marker(1), -200);
assert_eq!(trigen.out_point_marker(2), -300);
assert_eq!(trigen.out_segment_marker(0), -10);
assert_eq!(trigen.out_segment_marker(1), -20);
assert_eq!(trigen.out_segment_marker(2), -30);
assert_eq!(trigen.out_cell_point(0, 0), 0);
assert_eq!(trigen.out_cell_point(0, 1), 1);
assert_eq!(trigen.out_cell_point(0, 2), 2);
assert_eq!(trigen.out_cell_marker(0), 0);
assert_eq!(trigen.out_cell_marker(1), 0);
assert_eq!(trigen.out_cell_marker(2), 0);
assert_eq!(trigen.out_voronoi_npoint(), 0);
assert_eq!(trigen.out_voronoi_nedge(), 0);
Ok(())
}
#[test]
fn mesh_2_no_steiner_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(4, Some(4), None, None)?;
trigen
.set_point(0, -100, 0.0, 0.0)?
.set_point(1, -200, 1.0, 0.0)?
.set_point(2, -300, 1.0, 1.0)?
.set_point(3, -400, 0.0, 1.0)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, -20, 1, 2)?
.set_segment(2, -30, 2, 3)?
.set_segment(3, -40, 3, 0)?;
trigen.generate_mesh(false, false, false, Some(0.1), None)?;
if SAVE_FIGURE {
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None);
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/test_mesh_2_no_steiner.svg")?;
}
assert_eq!(trigen.out_npoint(), 5);
assert_eq!(trigen.out_nsegment(), 4);
assert_eq!(trigen.out_ncell(), 4);
assert_eq!(trigen.out_cell_npoint(), 3);
assert_eq!(trigen.out_point_marker(0), -100);
assert_eq!(trigen.out_point_marker(1), -200);
assert_eq!(trigen.out_point_marker(2), -300);
assert_eq!(trigen.out_point_marker(3), -400);
assert_eq!(trigen.out_point_marker(4), 0);
let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)];
let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)];
let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)];
let mut sides3 = vec![trigen.out_segment_point(3, 0), trigen.out_segment_point(3, 1)];
sides0.sort();
sides1.sort();
sides2.sort();
sides3.sort();
assert_eq!(sides0, &[0, 1]);
assert_eq!(sides1, &[1, 2]);
assert_eq!(sides2, &[2, 3]);
assert_eq!(sides3, &[0, 3]);
assert_eq!(trigen.out_segment_marker(0), -10);
assert_eq!(trigen.out_segment_marker(1), -20);
assert_eq!(trigen.out_segment_marker(2), -30);
assert_eq!(trigen.out_segment_marker(3), -40);
Ok(())
}
#[test]
fn mesh_2_ok_steiner_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(4, Some(4), None, None)?;
trigen
.set_point(0, -100, 0.0, 0.0)?
.set_point(1, -200, 1.0, 0.0)?
.set_point(2, -300, 1.0, 1.0)?
.set_point(3, -400, 0.0, 1.0)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, -20, 1, 2)?
.set_segment(2, -30, 2, 3)?
.set_segment(3, -40, 3, 0)?;
trigen.generate_mesh(false, false, true, Some(0.1), None)?;
if SAVE_FIGURE {
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None);
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/test_mesh_2_ok_steiner.svg")?;
}
assert_eq!(trigen.out_npoint(), 13);
assert_eq!(trigen.out_nsegment(), 8);
assert_eq!(trigen.out_ncell(), 16);
assert_eq!(trigen.out_cell_npoint(), 3);
assert_eq!(trigen.out_point_marker(0), -100);
assert_eq!(trigen.out_point_marker(1), -200);
assert_eq!(trigen.out_point_marker(2), -300);
assert_eq!(trigen.out_point_marker(3), -400);
assert_eq!(trigen.out_point_marker(4), 0);
assert_eq!(trigen.out_point_marker(5), -40);
assert_eq!(trigen.out_point_marker(6), -10);
assert_eq!(trigen.out_point_marker(7), -30);
assert_eq!(trigen.out_point_marker(8), 0);
assert_eq!(trigen.out_point_marker(9), -20);
assert_eq!(trigen.out_point_marker(10), 0);
assert_eq!(trigen.out_point_marker(11), 0);
assert_eq!(trigen.out_point_marker(12), 0);
let mut sides0 = vec![trigen.out_segment_point(0, 0), trigen.out_segment_point(0, 1)];
let mut sides1 = vec![trigen.out_segment_point(1, 0), trigen.out_segment_point(1, 1)];
let mut sides2 = vec![trigen.out_segment_point(2, 0), trigen.out_segment_point(2, 1)];
let mut sides3 = vec![trigen.out_segment_point(3, 0), trigen.out_segment_point(3, 1)];
let mut sides4 = vec![trigen.out_segment_point(4, 0), trigen.out_segment_point(4, 1)];
let mut sides5 = vec![trigen.out_segment_point(5, 0), trigen.out_segment_point(5, 1)];
let mut sides6 = vec![trigen.out_segment_point(6, 0), trigen.out_segment_point(6, 1)];
let mut sides7 = vec![trigen.out_segment_point(7, 0), trigen.out_segment_point(7, 1)];
sides0.sort();
sides1.sort();
sides2.sort();
sides3.sort();
sides4.sort();
sides5.sort();
sides6.sort();
sides7.sort();
assert_eq!(sides0, &[1, 6]);
assert_eq!(sides1, &[2, 9]);
assert_eq!(sides2, &[3, 7]);
assert_eq!(sides3, &[0, 5]);
assert_eq!(sides4, &[3, 5]);
assert_eq!(sides5, &[0, 6]);
assert_eq!(sides6, &[2, 7]);
assert_eq!(sides7, &[1, 9]);
assert_eq!(trigen.out_segment_marker(0), -10);
assert_eq!(trigen.out_segment_marker(1), -20);
assert_eq!(trigen.out_segment_marker(2), -30);
assert_eq!(trigen.out_segment_marker(3), -40);
assert_eq!(trigen.out_segment_marker(4), -40);
assert_eq!(trigen.out_segment_marker(5), -10);
assert_eq!(trigen.out_segment_marker(6), -30);
assert_eq!(trigen.out_segment_marker(7), -20);
Ok(())
}
#[test]
fn get_methods_work_with_wrong_indices() -> Result<(), StrError> {
let trigen = Trigen::new(3, None, None, None)?;
assert_eq!(trigen.out_point(100, 0), 0.0);
assert_eq!(trigen.out_point(0, 100), 0.0);
assert_eq!(trigen.out_cell_marker(100), 0);
assert_eq!(trigen.out_voronoi_point(100, 0), 0.0);
assert_eq!(trigen.out_voronoi_point(0, 100), 0.0);
assert_eq!(trigen.out_voronoi_edge_point_a(100), 0,);
assert_eq!(format!("{:?}", trigen.out_voronoi_edge_point_b(100)), "Index(0)");
Ok(())
}
#[test]
fn draw_triangles_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(3, Some(3), None, None)?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 0.0, 1.0)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, -20, 1, 2)?
.set_segment(2, -30, 2, 0)?;
trigen.generate_mesh(false, true, false, Some(0.25), None)?;
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None);
if SAVE_FIGURE {
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/triangle_draw_triangles_works.svg")?;
}
Ok(())
}
#[test]
fn draw_voronoi_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(5, None, None, None)?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 1.0, 1.0)?
.set_point(3, 0, 0.0, 1.0)?
.set_point(4, 0, 0.5, 0.5)?;
trigen.generate_voronoi(false)?;
assert_eq!(trigen.out_voronoi_npoint(), 4);
let mut plot = Plot::new();
trigen.draw_voronoi(&mut plot);
if SAVE_FIGURE {
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/triangle_draw_voronoi_works.svg")?;
}
Ok(())
}
#[test]
fn mesh_3_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(4, Some(3), Some(1), None)?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 0.0, 1.0)?
.set_point(3, 0, 0.5, 0.5)?
.set_region(0, 1, 0.5, 0.2, None)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, -20, 1, 2)?
.set_segment(2, -30, 2, 0)?;
trigen.generate_mesh(false, true, false, Some(0.25), None)?;
assert_eq!(trigen.out_ncell(), 2);
assert_eq!(trigen.out_cell_marker(0), 1);
assert_eq!(trigen.out_cell_marker(1), 1);
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None);
if SAVE_FIGURE {
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/triangle_mesh_3_works.svg")?;
}
Ok(())
}
#[test]
fn mesh_4_works() -> Result<(), StrError> {
let mut trigen = Trigen::new(12, Some(10), Some(2), Some(1))?;
trigen
.set_point(0, 0, 0.0, 0.0)?
.set_point(1, 0, 1.0, 0.0)?
.set_point(2, 0, 1.0, 1.0)?
.set_point(3, 0, 0.0, 1.0)?
.set_point(4, 0, 0.2, 0.2)?
.set_point(5, 0, 0.8, 0.2)?
.set_point(6, 0, 0.8, 0.8)?
.set_point(7, 0, 0.2, 0.8)?
.set_point(8, 0, 0.0, 0.5)?
.set_point(9, 0, 0.2, 0.5)?
.set_point(10, 0, 0.8, 0.5)?
.set_point(11, 0, 1.0, 0.5)?
.set_region(0, 111, 0.1, 0.1, None)?
.set_region(1, 222, 0.1, 0.9, None)?
.set_hole(0, 0.5, 0.5)?;
trigen
.set_segment(0, -10, 0, 1)?
.set_segment(1, 0, 1, 2)?
.set_segment(2, 0, 2, 3)?
.set_segment(3, 0, 3, 0)?
.set_segment(4, 0, 4, 5)?
.set_segment(5, 0, 5, 6)?
.set_segment(6, 0, 6, 7)?
.set_segment(7, 0, 7, 4)?
.set_segment(8, 0, 8, 9)?
.set_segment(9, 0, 10, 11)?;
trigen.generate_mesh(false, true, true, None, None)?;
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, Some(12.0), Some(20.0), None);
if SAVE_FIGURE {
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/triangle_mesh_4_works.svg")?;
}
assert_eq!(trigen.out_ncell(), 14);
assert_eq!(trigen.out_cell_marker(0), 111);
assert_eq!(trigen.out_cell_marker(12), 222);
Ok(())
}
#[test]
fn tri_from_input_data_works() -> Result<(), StrError> {
let data = InputDataTriMesh {
points: vec![
(0, 0.0, 0.0),
(0, 1.0, 0.0),
(0, 1.0, 1.0),
(0, 0.0, 1.0),
(0, 0.2, 0.2),
(0, 0.8, 0.2),
(0, 0.8, 0.8),
(0, 0.2, 0.8),
(0, 0.0, 0.5),
(0, 0.2, 0.5),
(0, 0.8, 0.5),
(0, 1.0, 0.5),
],
segments: vec![
(-1, 0, 1),
(-1, 1, 2),
(-1, 2, 3),
(-1, 3, 0),
(-1, 4, 5),
(-1, 5, 6),
(-1, 6, 7),
(-1, 7, 4),
(-1, 8, 9),
(-1, 10, 11),
],
holes: vec![(0.5, 0.5)],
regions: vec![(1, 0.1, 0.1, None), (2, 0.1, 0.9, Some(0.001))],
};
let trigen = Trigen::from_input_data(&data)?;
trigen.generate_mesh(false, false, true, None, None)?;
assert_eq!(trigen.out_npoint(), 305);
assert_eq!(trigen.out_ncell(), 525);
if SAVE_FIGURE {
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, false, false, false, false, None, None, None);
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/test_tri_from_input_data_works.svg")?;
}
Ok(())
}
#[test]
fn tri_write_msh_file_works() -> Result<(), StrError> {
let data = InputDataTriMesh {
points: vec![(-1, 0.0, 0.0), (-2, 1.0, 0.0), (-3, 1.0, 1.0), (-4, 0.0, 1.0)],
segments: vec![(-10, 0, 1), (-20, 1, 2), (-30, 2, 3), (-40, 3, 0)],
holes: vec![],
regions: vec![(1, 0.1, 0.1, None)],
};
let trigen = Trigen::from_input_data(&data)?;
trigen.generate_mesh(false, false, true, Some(0.45), None)?;
if SAVE_FIGURE {
let mut plot = Plot::new();
trigen.draw_triangles(&mut plot, true, true, true, true, None, None, None);
plot.set_equal_axes(true)
.set_figure_size_points(600.0, 600.0)
.save("/tmp/tritet/test_tri_write_msh_file_works.svg")?;
}
let file_path = "/tmp/tritet/test_tri_write_msh_file_works.msh";
trigen.write_msh(file_path)?;
let contents = fs::read_to_string(file_path).map_err(|_| "cannot open file")?;
let correct = "# header\n\
# ndim npoint ncell nmarked_edge nmarked_face\n\
2 5 4 4 0\n\
\n\
# points\n\
# id marker x y\n\
0 -1 0.0 0.0\n\
1 -2 1.0 0.0\n\
2 -3 1.0 1.0\n\
3 -4 0.0 1.0\n\
4 0 0.5 0.5\n\
\n\
# cells\n\
# id marker kind points\n\
0 1 tri3 1 2 4\n\
1 1 tri3 3 0 4\n\
2 1 tri3 4 2 3\n\
3 1 tri3 0 1 4\n\
\n\
# marked edges\n\
# marker p1 p2\n\
-10 1 0\n\
-20 2 1\n\
-30 3 2\n\
-40 0 3\n\
";
assert_eq!(contents, correct);
Ok(())
}
}