Merge pull request #388 from hannobraun/approx

Clean up approximation code
This commit is contained in:
Hanno Braun 2022-03-22 15:51:12 +01:00 committed by GitHub
commit f5522ec19c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 119 deletions

View File

@ -2,9 +2,9 @@ use std::collections::HashSet;
use fj_math::{Point, Scalar, Segment};
use crate::topology::{Cycle, Edge, Face, Vertex};
use crate::topology::{Cycle, Face, Vertex};
/// An approximation of an edge, multiple edges, or a face
/// The approximation of a face
#[derive(Debug, PartialEq)]
pub struct Approximation {
/// All points that make up the approximation
@ -24,40 +24,11 @@ pub struct Approximation {
}
impl Approximation {
/// Compute an approximate for an edge
/// Compute the approximation of a face
///
/// `tolerance` defines how far the approximation is allowed to deviate from
/// the actual edge.
pub fn for_edge(edge: &Edge, tolerance: Scalar) -> Self {
let mut points = Vec::new();
edge.curve().approx(tolerance, &mut points);
approximate_edge(points, edge.vertices())
}
/// Compute an approximation for a cycle
///
/// `tolerance` defines how far the approximation is allowed to deviate from
/// the actual cycle.
pub fn for_cycle(cycle: &Cycle, tolerance: Scalar) -> Self {
let mut points = HashSet::new();
let mut segments = HashSet::new();
for edge in cycle.edges() {
let approx = Self::for_edge(&edge, tolerance);
points.extend(approx.points);
segments.extend(approx.segments);
}
Self { points, segments }
}
/// Compute an approximation for a face
///
/// `tolerance` defines how far the approximation is allowed to deviate from
/// the actual edges.
pub fn for_face(face: &Face, tolerance: Scalar) -> Self {
/// the actual face.
pub fn new(face: &Face, tolerance: Scalar) -> Self {
// Curved faces whose curvature is not fully defined by their edges
// are not supported yet. For that reason, we can fully ignore `face`'s
// `surface` field and just pass the edges to `Self::for_edges`.
@ -70,14 +41,23 @@ impl Approximation {
// doesn't need to be handled here, is a sphere. A spherical face would
// would need to provide its own approximation, as the edges that bound
// it have nothing to do with its curvature.
let mut points = HashSet::new();
let mut segments = HashSet::new();
for cycle in face.cycles() {
let approx = Self::for_cycle(&cycle, tolerance);
let cycle_points = approximate_cycle(&cycle, tolerance);
points.extend(approx.points);
segments.extend(approx.segments);
let mut cycle_segments = Vec::new();
for segment in cycle_points.windows(2) {
let p0 = segment[0];
let p1 = segment[1];
cycle_segments.push(Segment::from([p0, p1]));
}
points.extend(cycle_points);
segments.extend(cycle_segments);
}
Self { points, segments }
@ -87,7 +67,7 @@ impl Approximation {
fn approximate_edge(
mut points: Vec<Point<3>>,
vertices: Option<[Vertex; 2]>,
) -> Approximation {
) -> Vec<Point<3>> {
// Insert the exact vertices of this edge into the approximation. This means
// we don't rely on the curve approximation to deliver accurate
// representations of these vertices, which they might not be able to do.
@ -101,28 +81,35 @@ fn approximate_edge(
points.push(b.point());
}
let mut segment_points = points.clone();
if vertices.is_none() {
// The edge has no vertices, which means it connects to itself. We need
// to reflect that in the approximation.
if let Some(&point) = points.first() {
segment_points.push(point);
points.push(point);
}
}
let mut segments = HashSet::new();
for segment in segment_points.windows(2) {
let p0 = segment[0];
let p1 = segment[1];
segments.insert(Segment::from([p0, p1]));
points
}
Approximation {
points: points.into_iter().collect(),
segments,
/// Compute an approximation for a cycle
///
/// `tolerance` defines how far the approximation is allowed to deviate from the
/// actual cycle.
pub fn approximate_cycle(cycle: &Cycle, tolerance: Scalar) -> Vec<Point<3>> {
let mut points = Vec::new();
for edge in cycle.edges() {
let mut edge_points = Vec::new();
edge.curve().approx(tolerance, &mut edge_points);
points.extend(approximate_edge(edge_points, edge.vertices()));
}
points.dedup();
points
}
#[cfg(test)]
@ -136,13 +123,10 @@ mod tests {
topology::{Cycle, Face, Vertex},
};
use super::{approximate_edge, Approximation};
use super::Approximation;
#[test]
fn for_edge() {
// Doesn't test `Approximation::for_edge` directly, but that method only
// contains a bit of additional glue code that is not critical.
fn approximate_edge() {
let mut shape = Shape::new();
let a = Point::from([1., 2., 3.]);
@ -160,70 +144,15 @@ mod tests {
// Regular edge
assert_eq!(
approximate_edge(
super::approximate_edge(
points.clone(),
Some([v1.get().clone(), v2.get().clone()])
),
Approximation {
points: set![a, b, c, d],
segments: set![
Segment::from([a, b]),
Segment::from([b, c]),
Segment::from([c, d]),
],
}
vec![a, b, c, d],
);
// Continuous edge
assert_eq!(
approximate_edge(points, None),
Approximation {
points: set![b, c],
segments: set![Segment::from([b, c]), Segment::from([c, b])],
}
);
}
#[test]
fn for_cycle() {
let tolerance = Scalar::ONE;
let mut shape = Shape::new();
let a = Point::from([1., 2., 3.]);
let b = Point::from([2., 3., 5.]);
let c = Point::from([3., 5., 8.]);
let v1 = shape.geometry().add_point(a);
let v2 = shape.geometry().add_point(b);
let v3 = shape.geometry().add_point(c);
let v1 = shape.topology().add_vertex(Vertex { point: v1 }).unwrap();
let v2 = shape.topology().add_vertex(Vertex { point: v2 }).unwrap();
let v3 = shape.topology().add_vertex(Vertex { point: v3 }).unwrap();
let ab = shape
.topology()
.add_line_segment([v1.clone(), v2.clone()])
.unwrap();
let bc = shape.topology().add_line_segment([v2, v3.clone()]).unwrap();
let ca = shape.topology().add_line_segment([v3, v1]).unwrap();
let cycle = Cycle {
edges: vec![ab, bc, ca],
};
assert_eq!(
Approximation::for_cycle(&cycle, tolerance),
Approximation {
points: set![a, b, c],
segments: set![
Segment::from([a, b]),
Segment::from([b, c]),
Segment::from([c, a]),
],
}
);
assert_eq!(super::approximate_edge(points, None), vec![b, c, b],);
}
#[test]
@ -272,7 +201,7 @@ mod tests {
};
assert_eq!(
Approximation::for_face(&face, tolerance),
Approximation::new(&face, tolerance),
Approximation {
points: set![a, b, c, d],
segments: set![

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use fj_math::{Scalar, Transform, Triangle, Vector};
use fj_math::{Scalar, Segment, Transform, Triangle, Vector};
use crate::{
geometry::{Surface, SweptCurve},
@ -8,7 +8,7 @@ use crate::{
topology::{Cycle, Edge, Face, Vertex},
};
use super::approximation::Approximation;
use super::approximation::approximate_cycle;
/// Create a new shape by sweeping an existing one
pub fn sweep_shape(
@ -144,11 +144,12 @@ pub fn sweep_shape(
// This is the last piece of code that still uses the triangle
// representation.
let approx =
Approximation::for_cycle(&cycle_source.get(), tolerance);
let approx = approximate_cycle(&cycle_source.get(), tolerance);
let mut quads = Vec::new();
for segment in approx.segments {
for segment in approx.windows(2) {
let segment = Segment::from_points([segment[0], segment[1]]);
let [v0, v1] = segment.points();
let [v3, v2] = {
let segment = Transform::translation(path)

View File

@ -25,7 +25,7 @@ pub fn triangulate(
match &*face {
Face::Face { surface, color, .. } => {
let surface = surface.get();
let approx = Approximation::for_face(&face, tolerance);
let approx = Approximation::new(&face, tolerance);
let points: Vec<_> = approx
.points