Skip to content

Commit

Permalink
Merge pull request #427 from hannobraun/approx
Browse files Browse the repository at this point in the history
Make approximation more flexible
  • Loading branch information
hannobraun authored Apr 4, 2022
2 parents 204ca8f + d3eafab commit 11816f3
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 61 deletions.
110 changes: 59 additions & 51 deletions fj-kernel/src/algorithms/approximation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@ use fj_math::{Point, Scalar, Segment};

use crate::topology::{Cycle, Face, Vertex};

/// The approximation of a face
/// An approximation of a [`Face`]
#[derive(Debug, PartialEq)]
pub struct Approximation {
pub struct FaceApprox {
/// All points that make up the approximation
///
/// These could be actual vertices from the model, points that approximate
/// an edge, or points that approximate a face.
pub points: HashSet<Point<3>>,

/// Segments that approximate edges
///
/// Every approximation will involve edges, typically, and these are
/// approximated by these segments.
///
/// All the points of these segments will also be available in the `points`
/// field of this struct.
pub segments: HashSet<Segment<3>>,
/// The approximation of the face's cycles
pub cycles: HashSet<CycleApprox>,
}

impl Approximation {
impl FaceApprox {
/// Compute the approximation of a face
///
/// `tolerance` defines how far the approximation is allowed to deviate from
Expand All @@ -43,24 +37,60 @@ impl Approximation {
// it have nothing to do with its curvature.

let mut points = HashSet::new();
let mut segments = HashSet::new();
let mut cycles = HashSet::new();

for cycle in face.all_cycles() {
let cycle_points = approximate_cycle(&cycle, tolerance);
let cycle = CycleApprox::new(&cycle, tolerance);

let mut cycle_segments = Vec::new();
for segment in cycle_points.windows(2) {
let p0 = segment[0];
let p1 = segment[1];
points.extend(cycle.points.iter().copied());
cycles.insert(cycle);
}

cycle_segments.push(Segment::from([p0, p1]));
}
Self { points, cycles }
}
}

/// An approximation of a [`Cycle`]
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct CycleApprox {
/// The points that approximate the cycle
pub points: Vec<Point<3>>,
}

impl CycleApprox {
/// Compute the approximation of a cycle
///
/// `tolerance` defines how far the approximation is allowed to deviate from
/// the actual face.
pub fn new(cycle: &Cycle, tolerance: Scalar) -> Self {
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();

Self { points }
}

points.extend(cycle_points);
segments.extend(cycle_segments);
/// Construct the segments that approximate the cycle
pub fn segments(&self) -> Vec<Segment<3>> {
let mut segments = Vec::new();

for segment in self.points.windows(2) {
// This can't panic, as we passed `2` to `windows`. Can be cleaned
// up, once `array_windows` is stable.
let p0 = segment[0];
let p1 = segment[1];

segments.push(Segment::from([p0, p1]));
}

Self { points, segments }
segments
}
}

Expand Down Expand Up @@ -93,28 +123,9 @@ fn approximate_edge(
points
}

/// 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)]
mod tests {
use fj_math::{Point, Scalar, Segment};
use fj_math::{Point, Scalar};
use map_macro::set;

use crate::{
Expand All @@ -123,7 +134,7 @@ mod tests {
topology::{Face, Vertex},
};

use super::Approximation;
use super::{CycleApprox, FaceApprox};

#[test]
fn approximate_edge() -> anyhow::Result<()> {
Expand Down Expand Up @@ -167,15 +178,12 @@ mod tests {
.build()?;

assert_eq!(
Approximation::new(&face.get(), tolerance),
Approximation {
FaceApprox::new(&face.get(), tolerance),
FaceApprox {
points: set![a, b, c, d],
segments: set![
Segment::from([a, b]),
Segment::from([b, c]),
Segment::from([c, d]),
Segment::from([d, a]),
],
cycles: set![CycleApprox {
points: vec![a, b, c, d, a],
}],
}
);

Expand Down
3 changes: 2 additions & 1 deletion fj-kernel/src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod sweep;
mod triangulation;

pub use self::{
approximation::Approximation, sweep::sweep_shape,
approximation::{CycleApprox, FaceApprox},
sweep::sweep_shape,
triangulation::triangulate,
};
10 changes: 4 additions & 6 deletions fj-kernel/src/algorithms/sweep.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::collections::HashMap;

use fj_math::{Scalar, Segment, Transform, Triangle, Vector};
use fj_math::{Scalar, Transform, Triangle, Vector};

use crate::{
geometry::{Surface, SweptCurve},
shape::{Handle, Shape},
topology::{Cycle, Edge, Face, Vertex},
};

use super::approximation::approximate_cycle;
use super::CycleApprox;

/// Create a new shape by sweeping an existing one
pub fn sweep_shape(
Expand Down Expand Up @@ -135,12 +135,10 @@ pub fn sweep_shape(
// This is the last piece of code that still uses the triangle
// representation.

let approx = approximate_cycle(&cycle_source.get(), tolerance);
let approx = CycleApprox::new(&cycle_source.get(), tolerance);

let mut quads = Vec::new();
for segment in approx.windows(2) {
let segment = Segment::from_points([segment[0], segment[1]]);

for segment in approx.segments() {
let [v0, v1] = segment.points();
let [v3, v2] = {
let segment = Transform::translation(path)
Expand Down
8 changes: 5 additions & 3 deletions fj-kernel/src/algorithms/triangulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use spade::HasPosition;

use crate::{geometry, shape::Shape, topology::Face};

use super::Approximation;
use super::FaceApprox;

/// Triangulate a shape
pub fn triangulate(
Expand All @@ -25,7 +25,7 @@ pub fn triangulate(
match &face {
Face::Face { surface, color, .. } => {
let surface = surface.get();
let approx = Approximation::new(&face, tolerance);
let approx = FaceApprox::new(&face, tolerance);

let points: Vec<_> = approx
.points
Expand All @@ -38,8 +38,10 @@ pub fn triangulate(
.collect();

let segments: Vec<_> = approx
.segments
.cycles
.into_iter()
.map(|cycle| cycle.segments())
.flatten()
.map(|segment| {
let [a, b] = segment.points();

Expand Down

0 comments on commit 11816f3

Please sign in to comment.