Skip to content

Commit

Permalink
Make mesh creation APIs fallible (#52)
Browse files Browse the repository at this point in the history
* Make mesh creation APIs fallible

* Fix more tests
  • Loading branch information
msvbg committed May 23, 2024
1 parent dc1858a commit 1d85afc
Show file tree
Hide file tree
Showing 15 changed files with 86 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ serde = { version = "1.0", features = ["derive"], optional = true }
spade = "2.2"
geo = "0.26.0"
log = "0.4"
thiserror = "1"

[dev-dependencies]
criterion = "0.5"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn main() {
Polygon::new(vec![15, 18, 19, 16], true), // 5
Polygon::new(vec![11, 17, 20, 21], true), // 6
],
);
).unwrap();

// Get the path between two points
let from = Vec2::new(12.0, 0.0);
Expand Down
4 changes: 3 additions & 1 deletion benches/baking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use criterion::{criterion_group, criterion_main, Criterion};
use polyanya::{Mesh, PolyanyaFile};

fn baking(c: &mut Criterion) {
let mut mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mut mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
c.bench_function(&"baking".to_string(), |b| {
b.iter(|| {
mesh.unbake();
Expand Down
8 changes: 6 additions & 2 deletions benches/is_in_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use glam::Vec2;
use polyanya::{Mesh, PolyanyaFile};

fn is_in_mesh(c: &mut Criterion) {
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
[
Vec2::new(575., 410.),
Vec2::new(728., 148.),
Expand All @@ -23,7 +25,9 @@ fn is_in_mesh(c: &mut Criterion) {
}

fn is_not_in_mesh(c: &mut Criterion) {
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
[
Vec2::new(0., 0.),
Vec2::new(297., 438.),
Expand Down
4 changes: 3 additions & 1 deletion benches/no_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use glam::Vec2;
use polyanya::{Mesh, PolyanyaFile};

fn no_path(c: &mut Criterion) {
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
[
(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),
(Vec2::new(0.0, 0.0), Vec2::new(575.0, 410.0)),
Expand Down
4 changes: 3 additions & 1 deletion benches/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ macro_rules! assert_delta {
}

fn get_path(c: &mut Criterion) {
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
[
(Vec2::new(993.0, 290.0), Vec2::new(34.0, 622.0), 1123.2226),
(Vec2::new(356.0, 166.0), Vec2::new(661.0, 441.0), 595.041),
Expand Down
4 changes: 3 additions & 1 deletion examples/aurora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ fn main() {
)
.expect("set up the subscriber");

let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh").into();
let mesh: Mesh = PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap();
assert_delta!(
mesh.path(Vec2::new(993.0, 290.0), Vec2::new(34.0, 622.0))
.unwrap(),
Expand Down
4 changes: 3 additions & 1 deletion examples/scenario_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ fn main() {
let mut args = std::env::args();
args.next();

let mesh: Mesh = PolyanyaFile::from_file(&args.next().unwrap()).into();
let mesh: Mesh = PolyanyaFile::from_file(&args.next().unwrap())
.try_into()
.unwrap();

for scenario in Scenarios::from_file(&args.next().unwrap()).0 {
mesh.path(scenario.from, scenario.to).unwrap();
Expand Down
4 changes: 3 additions & 1 deletion examples/tests-aurora-merged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ macro_rules! assert_delta {

#[cfg(test)]
fn aurora_mesh() -> Mesh {
PolyanyaFile::from_file("meshes/aurora-merged.mesh").into()
PolyanyaFile::from_file("meshes/aurora-merged.mesh")
.try_into()
.unwrap()
}

#[test]
Expand Down
4 changes: 3 additions & 1 deletion examples/tests-aurora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ macro_rules! assert_delta {

#[cfg(test)]
fn aurora_mesh() -> Mesh {
PolyanyaFile::from_file("meshes/aurora.mesh").into()
PolyanyaFile::from_file("meshes/aurora.mesh")
.try_into()
.unwrap()
}

#[test]
Expand Down
8 changes: 5 additions & 3 deletions src/input/polyanya_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::{self, BufRead, Read, Write};

use glam::Vec2;

use crate::{Mesh, Polygon, Vertex};
use crate::{Mesh, MeshError, Polygon, Vertex};

/// A mesh read from a Polyanya file in the format `mesh 2`.
///
Expand Down Expand Up @@ -138,8 +138,10 @@ impl PolyanyaFile {
}
}

impl From<PolyanyaFile> for Mesh {
fn from(value: PolyanyaFile) -> Self {
impl TryFrom<PolyanyaFile> for Mesh {
type Error = MeshError;

fn try_from(value: PolyanyaFile) -> Result<Self, Self::Error> {
Mesh::new(value.vertices, value.polygons)
}
}
Expand Down
29 changes: 23 additions & 6 deletions src/input/trimesh.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Mesh, Polygon, Vertex};
use crate::{Mesh, MeshError, Polygon, Vertex};
use glam::Vec2;
use std::cmp::Ordering;
use std::iter;
Expand Down Expand Up @@ -48,8 +48,10 @@ pub struct Trimesh {
pub triangles: Vec<[usize; 3]>,
}

impl From<Trimesh> for Mesh {
fn from(value: Trimesh) -> Self {
impl TryFrom<Trimesh> for Mesh {
type Error = MeshError;

fn try_from(value: Trimesh) -> Result<Self, Self::Error> {
let mut vertices: Vec<_> = to_vertices(&value);
let polygons = to_polygons(value.triangles);
let unordered_vertices = vertices.clone();
Expand All @@ -75,6 +77,10 @@ impl From<Trimesh> for Mesh {
}
});

if vertex.polygons.is_empty() {
return Err(MeshError::InvalidMesh);
}

// Add obstacles (-1) as vertex neighbors
let mut polygons_including_obstacles = vec![vertex.polygons[0]];
for polygon_index in vertex
Expand Down Expand Up @@ -162,7 +168,7 @@ mod tests {
use super::*;

#[test]
fn generation_from_trimesh_is_same_as_regular() {
fn generation_from_trimesh_is_same_as_regular() -> Result<(), MeshError> {
let regular_mesh = Mesh::new(
vec![
Vertex::new(Vec2::new(1., 1.), vec![0, 4, -1]), // 0
Expand All @@ -179,7 +185,7 @@ mod tests {
Polygon::new(vec![1, 5, 3], false), // 3
Polygon::new(vec![0, 4, 3], false), // 4
],
);
)?;
let from_trimesh: Mesh = Trimesh {
vertices: vec![
Vec2::new(1., 1.),
Expand All @@ -191,7 +197,7 @@ mod tests {
],
triangles: vec![[0, 1, 4], [1, 2, 5], [5, 2, 3], [1, 5, 3], [0, 4, 3]],
}
.into();
.try_into()?;
assert_eq!(regular_mesh.polygons, from_trimesh.polygons);
for (index, (expected_vertex, actual_vertex)) in regular_mesh
.vertices
Expand Down Expand Up @@ -227,6 +233,17 @@ mod tests {
regular_mesh.vertices, from_trimesh.vertices
);
}
Ok(())
}

#[test]
fn isolated_vertex_fails() {
let trimesh: Result<Mesh, _> = Trimesh {
vertices: vec![Vec2::new(1., 1.)],
triangles: vec![],
}
.try_into();
assert!(matches!(trimesh, Err(MeshError::InvalidMesh)));
}

fn wrap_to_first(polygons: &[isize], pred: impl Fn(&isize) -> bool) -> Option<Vec<isize>> {
Expand Down
25 changes: 23 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use glam::Vec2;
use helpers::Vec2Helper;
use instance::{EdgeSide, InstanceStep};
use log::error;
use thiserror::Error;
#[cfg(feature = "tracing")]
use tracing::instrument;

Expand Down Expand Up @@ -121,6 +122,17 @@ impl Bounded for BoundedPolygon {
}
}

/// Errors that can happen when working creating a [`Mesh`]
#[derive(Error, Debug, Copy, Clone, PartialEq)]
pub enum MeshError {
/// The mesh is empty.
#[error("The mesh is empty")]
EmptyMesh,
/// The mesh is invalid, such as having a vertex that does not belong to any polygon.
#[error("The mesh is invalid")]
InvalidMesh,
}

impl Mesh {
/// Remove pre-computed optimizations from the mesh. Call this if you modified the [`Mesh`].
#[inline]
Expand Down Expand Up @@ -208,7 +220,10 @@ impl Mesh {
}

/// Create a `Mesh` from a list of [`Vertex`] and [`Polygon`].
pub fn new(vertices: Vec<Vertex>, polygons: Vec<Polygon>) -> Mesh {
pub fn new(vertices: Vec<Vertex>, polygons: Vec<Polygon>) -> Result<Self, MeshError> {
if vertices.is_empty() || polygons.is_empty() {
return Err(MeshError::EmptyMesh);
}
let mut mesh = Mesh {
vertices,
polygons,
Expand All @@ -219,7 +234,7 @@ impl Mesh {
// just to not get a warning on the mut borrow. should be pretty much free anyway
#[cfg(feature = "no-default-baking")]
mesh.unbake();
mesh
Ok(mesh)
}

/// Compute a path between two points.
Expand Down Expand Up @@ -744,6 +759,12 @@ mod tests {
);
}

#[test]
fn empty_mesh_fails() {
let mesh = Mesh::new(vec![], vec![]);
assert!(matches!(mesh, Err(crate::MeshError::EmptyMesh)));
}

fn mesh_from_paper() -> Mesh {
Mesh {
vertices: vec![
Expand Down
4 changes: 3 additions & 1 deletion tests/arena-merged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ macro_rules! assert_delta {
}

fn arena_mesh() -> Mesh {
PolyanyaFile::from_file("meshes/arena-merged.mesh").into()
PolyanyaFile::from_file("meshes/arena-merged.mesh")
.try_into()
.unwrap()
}

#[test]
Expand Down
4 changes: 3 additions & 1 deletion tests/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ macro_rules! assert_delta {
}

fn arena_mesh() -> Mesh {
PolyanyaFile::from_file("meshes/arena.mesh").into()
PolyanyaFile::from_file("meshes/arena.mesh")
.try_into()
.unwrap()
}

#[test]
Expand Down

0 comments on commit 1d85afc

Please sign in to comment.