Skip to content

Commit

Permalink
can build mesh from edge lists
Browse files Browse the repository at this point in the history
  • Loading branch information
mockersf committed Aug 8, 2023
1 parent 40607b7 commit e644c97
Show file tree
Hide file tree
Showing 6 changed files with 1,067 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ glam = { version = "0.24", features = ["approx"] }
smallvec = { version = "1.9", features = ["union", "const_generics"] }
bvh2d = { version = "0.3", git = "https://github.com/mockersf/bvh2d" }
serde = { version = "1.0", features = ["derive"], optional = true }
spade = "2.2"

[dev-dependencies]
criterion = "0.5"
Expand All @@ -51,3 +52,7 @@ harness = false
[[bench]]
name = "baking"
harness = false

[[bench]]
name = "triangulation"
harness = false
141 changes: 141 additions & 0 deletions benches/triangulation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use glam::vec2;
use polyanya::{Mesh, Triangulation};

fn triangulation(c: &mut Criterion) {
c.bench_function(&"triangulation".to_string(), |b| {
b.iter(|| {
// Equivalent to the arena mesh
let mut triangulation = Triangulation::from_outer_edges(vec![
vec2(1., 3.),
vec2(2., 3.),
vec2(2., 2.),
vec2(3., 2.),
vec2(3., 1.),
vec2(15., 1.),
vec2(15., 3.),
vec2(18., 3.),
vec2(18., 2.),
vec2(19., 2.),
vec2(19., 1.),
vec2(20., 1.),
vec2(20., 2.),
vec2(23., 2.),
vec2(23., 1.),
vec2(26., 1.),
vec2(26., 3.),
vec2(29., 3.),
vec2(29., 2.),
vec2(30., 2.),
vec2(30., 1.),
vec2(31., 1.),
vec2(31., 3.),
vec2(34., 3.),
vec2(34., 2.),
vec2(35., 2.),
vec2(35., 1.),
vec2(47., 1.),
vec2(47., 3.),
vec2(48., 3.),
vec2(48., 15.),
vec2(47., 15.),
vec2(47., 19.),
vec2(48., 19.),
vec2(48., 31.),
vec2(47., 31.),
vec2(47., 35.),
vec2(48., 35.),
vec2(48., 47.),
vec2(47., 47.),
vec2(47., 48.),
vec2(35., 48.),
vec2(35., 47.),
vec2(31., 47.),
vec2(31., 48.),
vec2(30., 48.),
vec2(30., 47.),
vec2(29., 47.),
vec2(29., 46.),
vec2(26., 46.),
vec2(26., 48.),
vec2(24., 48.),
vec2(24., 47.),
vec2(23., 47.),
vec2(23., 46.),
vec2(20., 46.),
vec2(20., 48.),
vec2(19., 48.),
vec2(19., 47.),
vec2(15., 47.),
vec2(15., 48.),
vec2(3., 48.),
vec2(3., 47.),
vec2(1., 47.),
vec2(1., 35.),
vec2(2., 35.),
vec2(2., 34.),
vec2(3., 34.),
vec2(3., 31.),
vec2(1., 31.),
vec2(1., 30.),
vec2(3., 30.),
vec2(3., 27.),
vec2(2., 27.),
vec2(2., 26.),
vec2(1., 26.),
vec2(1., 23.),
vec2(2., 23.),
vec2(2., 18.),
vec2(3., 18.),
vec2(3., 15.),
vec2(1., 15.),
]);

triangulation.add_obstacle(vec![
vec2(15., 15.),
vec2(19., 15.),
vec2(19., 18.),
vec2(18., 18.),
vec2(18., 19.),
vec2(15., 19.),
]);
triangulation.add_obstacle(vec![
vec2(31., 15.),
vec2(35., 15.),
vec2(35., 18.),
vec2(34., 18.),
vec2(34., 19.),
vec2(31., 19.),
]);
triangulation.add_obstacle(vec![
vec2(15., 31.),
vec2(19., 31.),
vec2(19., 34.),
vec2(18., 34.),
vec2(18., 35.),
vec2(15., 35.),
]);
triangulation.add_obstacle(vec![
vec2(31., 31.),
vec2(35., 31.),
vec2(35., 34.),
vec2(34., 34.),
vec2(34., 35.),
vec2(31., 35.),
]);
triangulation.add_obstacle(vec![
vec2(23., 10.),
vec2(23., 8.),
vec2(24., 8.),
vec2(24., 7.),
vec2(26., 7.),
vec2(26., 10.),
]);
let mesh: Mesh = triangulation.into();
black_box(mesh);
})
});
}

criterion_group!(benches, triangulation);
criterion_main!(benches);
1 change: 1 addition & 0 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod polyanya_file;
pub mod triangulation;
pub mod trimesh;
166 changes: 166 additions & 0 deletions src/input/triangulation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use std::collections::VecDeque;

use glam::{vec2, Vec2};
use hashbrown::HashMap;
use spade::{ConstrainedDelaunayTriangulation, Point2, Triangulation as SpadeTriangulation};

use crate::{
helpers::{line_intersect_segment, Vec2Helper},
Mesh, Polygon, Vertex,
};

/// An helper to create a [`Mesh`] from a list of edges and obstacle, using a constrained Delaunay triangulation.
#[derive(Debug, Clone)]
pub struct Triangulation {
edges: Vec<Vec2>,
obstacles: Vec<Vec<Vec2>>,
}

impl Triangulation {
/// Create a new triangulation from a the list of points on its outer edges.
pub fn from_outer_edges(edges: Vec<Vec2>) -> Triangulation {
Self {
edges,
obstacles: Default::default(),
}
}

/// Add an obstacle delimited by the list of points on its edges.
pub fn add_obstacle(&mut self, edges: Vec<Vec2>) {
self.obstacles.push(edges);
}

#[inline]
fn add_constraint_edges(
cdt: &mut ConstrainedDelaunayTriangulation<Point2<f32>>,
edges: Vec<Vec2>,
) -> (Vec<(Vec2, Vec2)>, (Vec2, Vec2)) {
let mut edge_iter = edges.iter().peekable();
let mut vertex_pairs = Vec::new();
let mut aabb_min = edges[0];
let mut aabb_max = edges[0];
loop {
let from = edge_iter.next().unwrap();
let next = edge_iter.peek();

aabb_min = aabb_min.min(*from);
aabb_max = aabb_max.max(*from);

if let Some(next) = next {
cdt.add_constraint_edge(
Point2 {
x: from.x,
y: from.y,
},
Point2 {
x: next.x,
y: next.y,
},
)
.unwrap();
vertex_pairs.push((*from, **next));
} else {
cdt.add_constraint_edge(
Point2 {
x: from.x,
y: from.y,
},
Point2 {
x: edges[0].x,
y: edges[0].y,
},
)
.unwrap();
vertex_pairs.push((*from, edges[0]));
break;
}
}

(vertex_pairs, (aabb_min, aabb_max))
}
}

// Check if a point is in a polygon, first by checking its AABB, then by checking the number of times
// a ray from the point to the top of the AABB intersects the polygon.
#[inline]
fn in_polygon(point: Vec2, edges: &Vec<(Vec2, Vec2)>, aabb: (Vec2, Vec2)) -> bool {
if point.x < aabb.0.x || point.x > aabb.1.x || point.y < aabb.0.y || point.y > aabb.1.y {
return false;
}
let mut parallel = 0;
let intersect = edges
.iter()
.filter(|edge| {
let start = point;
let far = point + vec2(0.0, aabb.1.y + 1.0);
if edge.0.on_segment((start, far)) && edge.1.on_segment((start, far)) {
parallel += 1;
}
line_intersect_segment((start, far), **edge)
.map(|i| i.y > start.y)
.unwrap_or(false)
})
.count();
(intersect - parallel) % 2 == 1
}

impl From<Triangulation> for Mesh {
fn from(value: Triangulation) -> Self {
let mut cdt = ConstrainedDelaunayTriangulation::<Point2<f32>>::new();
let (outer_edges, aabb_outer) = Triangulation::add_constraint_edges(&mut cdt, value.edges);

let mut obstacles = vec![];
for obstacle in value.obstacles {
obstacles.push(Triangulation::add_constraint_edges(&mut cdt, obstacle));
}

let mut face_to_polygon = HashMap::new();
let polygons = cdt
.inner_faces()
.filter_map(|face| {
let center = face.center();
let center = vec2(center.x, center.y);
(in_polygon(center, &outer_edges, aabb_outer)
&& obstacles
.iter()
.map(|(edges, aabb)| !in_polygon(center, edges, *aabb))
.all(|b| b))
.then(|| {
face_to_polygon.insert(face.index(), face_to_polygon.len() as isize);
Polygon::new(
face.vertices()
.iter()
.map(|vertex| vertex.index() as u32)
.collect(),
// TODO: can this be set to the correct value
false,
)
})
})
.collect::<Vec<_>>();

let vertices = cdt
.vertices()
.map(|point| {
let mut neighbour_polygons = point
.out_edges()
.map(|out_edge| {
face_to_polygon
.get(&out_edge.face().index())
.cloned()
.unwrap_or(-1)
})
.collect::<VecDeque<_>>();
while neighbour_polygons[0] == -1 {
neighbour_polygons.rotate_left(1);
}
let mut neighbour_polygons: Vec<_> = neighbour_polygons.into();
neighbour_polygons.dedup();
let point = point.position();
Vertex::new(vec2(point.x, point.y), neighbour_polygons)
})
.collect::<Vec<_>>();

Mesh::new(vertices, polygons)
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod primitives;
#[cfg(feature = "async")]
pub use async_helpers::FuturePath;
pub use input::polyanya_file::PolyanyaFile;
pub use input::triangulation::Triangulation;
pub use input::trimesh::Trimesh;
pub use primitives::{Polygon, Vertex};

Expand Down
Loading

0 comments on commit e644c97

Please sign in to comment.