Skip to content

Commit

Permalink
support arcs in sketches
Browse files Browse the repository at this point in the history
  • Loading branch information
antonok-edm committed Jan 5, 2023
1 parent c471900 commit c27a339
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 40 deletions.
16 changes: 16 additions & 0 deletions crates/fj-kernel/src/builder/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ pub trait CurveBuilder {
/// Update partial curve to be a circle, from the provided radius
fn update_as_circle_from_radius(&mut self, radius: impl Into<Scalar>);

/// Update partial curve to be a circle, from the provided radius
fn update_as_circle_from_center_and_radius(
&mut self,
center: impl Into<Point<2>>,
radius: impl Into<Scalar>,
);

/// Update partial curve to be a line, from the provided points
fn update_as_line_from_points(&mut self, points: [impl Into<Point<2>>; 2]);
}
Expand All @@ -36,6 +43,15 @@ impl CurveBuilder for PartialCurve {
self.path = Some(SurfacePath::circle_from_radius(radius));
}

fn update_as_circle_from_center_and_radius(
&mut self,
center: impl Into<Point<2>>,
radius: impl Into<Scalar>,
) {
self.path =
Some(SurfacePath::circle_from_center_and_radius(center, radius));
}

fn update_as_line_from_points(&mut self, points: [impl Into<Point<2>>; 2]) {
let (path, _) = SurfacePath::line_from_points(points);
self.path = Some(path);
Expand Down
54 changes: 54 additions & 0 deletions crates/fj-kernel/src/builder/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub trait HalfEdgeBuilder {
/// Update partial half-edge to be a circle, from the given radius
fn update_as_circle_from_radius(&mut self, radius: impl Into<Scalar>);

/// Update partial half-edge to be an arc, spanning the given angle in
/// radians
fn update_as_arc(&mut self, angle_rad: impl Into<Scalar>);

/// Update partial half-edge to be a line segment, from the given points
fn update_as_line_segment_from_points(
&mut self,
Expand Down Expand Up @@ -79,6 +83,56 @@ impl HalfEdgeBuilder for PartialHalfEdge {
self.infer_global_form();
}

fn update_as_arc(&mut self, angle_rad: impl Into<Scalar>) {
let angle_rad = angle_rad.into();
if angle_rad <= -Scalar::TAU || angle_rad >= Scalar::TAU {
panic!("arc angle must be in the range (-360, 360)");
}
let points_surface = self.vertices.each_ref_ext().map(|vertex| {
vertex
.read()
.surface_form
.read()
.position
.expect("Can't infer arc without surface position")
});

let arc_circle_data = fj_math::ArcCircleData::from_endpoints_and_angle(
points_surface[0],
points_surface[1],
angle_rad,
);

let mut curve = self.curve();
curve.write().update_as_circle_from_center_and_radius(
arc_circle_data.center,
arc_circle_data.radius,
);

let path = curve
.read()
.path
.expect("Expected path that was just created");

let [a_curve, b_curve] = if arc_circle_data.flipped_construction {
[arc_circle_data.end_angle, arc_circle_data.start_angle]
} else {
[arc_circle_data.start_angle, arc_circle_data.end_angle]
}
.map(|coord| Point::from([coord]));

for (vertex, point_curve) in
self.vertices.each_mut_ext().zip_ext([a_curve, b_curve])
{
let mut vertex = vertex.write();
vertex.position = Some(point_curve);
vertex.surface_form.write().position =
Some(path.point_from_path_coords(point_curve));
}

self.infer_global_form();
}

fn update_as_line_segment_from_points(
&mut self,
surface: impl Into<Partial<Surface>>,
Expand Down
10 changes: 9 additions & 1 deletion crates/fj-kernel/src/geometry/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ pub enum SurfacePath {
impl SurfacePath {
/// Build a circle from the given radius
pub fn circle_from_radius(radius: impl Into<Scalar>) -> Self {
Self::circle_from_center_and_radius(Point::origin(), radius)
}

/// Build a circle from the given radius
pub fn circle_from_center_and_radius(
center: impl Into<Point<2>>,
radius: impl Into<Scalar>,
) -> Self {
let radius = radius.into();

Self::Circle(Circle::from_center_and_radius(Point::origin(), radius))
Self::Circle(Circle::from_center_and_radius(center.into(), radius))
}

/// Construct a line from two points
Expand Down
69 changes: 69 additions & 0 deletions crates/fj-math/src/circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,75 @@ impl<const D: usize> approx::AbsDiffEq for Circle<D> {
}
}

/// Calculated geometry that is useful when dealing with an arc
pub struct ArcCircleData {
/// Start point of the arc
pub start: Point<2>,
/// End point of the arc
pub end: Point<2>,
/// Center of the circle the arc is constructed on
pub center: Point<2>,
/// Radius of the circle the arc is constructed on
pub radius: Scalar,
/// Angle of `start` relative to `center`, in radians
///
/// Guaranteed to be less than `end_angle`.
pub start_angle: Scalar,
/// Angle of `end` relative to `center`, in radians
///
/// Guaranteed to be greater than `end_angle`.
pub end_angle: Scalar,
/// True if `start` and `end` were switched to ensure `end_angle` > `start_angle`
pub flipped_construction: bool,
}

impl ArcCircleData {
/// Constructs an [`ArcCircleData`] from two endpoints and the associated angle.
pub fn from_endpoints_and_angle(
p0: impl Into<Point<2>>,
p1: impl Into<Point<2>>,
angle: Scalar,
) -> Self {
use num_traits::Float;

let (p0, p1) = (p0.into(), p1.into());

let flipped_construction = angle <= Scalar::ZERO;
let angle_rad = angle.abs();

let [p0, p1] = if flipped_construction {
[p1, p0]
} else {
[p0, p1]
};
let [[x0, y0], [x1, y1]] = [p0, p1].map(|p| p.coords.components);
// https://math.stackexchange.com/questions/27535/how-to-find-center-of-an-arc-given-start-point-end-point-radius-and-arc-direc
// distance between endpoints
let d = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
// radius
let r = d / (2. * (angle_rad.into_f64() / 2.).sin());
// distance from center to midpoint between endpoints
let h = (r.powi(2) - (d.powi(2) / 4.)).sqrt();
// (u, v) is the unit normal in the direction of p1 - p0
let u = (x1 - x0) / d;
let v = (y1 - y0) / d;
// (cx, cy) is the center of the circle
let cx = ((x0 + x1) / 2.) - h * v;
let cy = ((y0 + y1) / 2.) + h * u;
let start_angle = (y0 - cy).atan2(x0 - cx);
let end_angle = (y1 - cy).atan2(x1 - cx);
Self {
start: p0,
end: p1,
center: Point::from([cx, cy]),
radius: r,
start_angle,
end_angle,
flipped_construction,
}
}
}

#[cfg(test)]
mod tests {
use std::f64::consts::{FRAC_PI_2, PI};
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ mod vector;

pub use self::{
aabb::Aabb,
circle::Circle,
circle::{ArcCircleData, Circle},
coordinates::{Uv, Xyz, T},
line::Line,
plane::Plane,
Expand Down
101 changes: 80 additions & 21 deletions crates/fj-operations/src/sketch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::ops::Deref;

use fj_interop::{debug::DebugInfo, mesh::Color};
use fj_kernel::{
builder::{FaceBuilder, HalfEdgeBuilder},
builder::{CycleBuilder, HalfEdgeBuilder},
insert::Insert,
objects::{Objects, Sketch},
partial::{
Expand Down Expand Up @@ -56,18 +56,55 @@ impl Shape for fj::Sketch {
}
}
fj::Chain::PolyChain(poly_chain) => {
let points = poly_chain
.to_segments()
.into_iter()
.map(|fj::SketchSegment::LineTo { point }| point)
.map(Point::from);

let mut face = PartialFace::default();
face.exterior.write().surface = Partial::from(surface);
face.update_exterior_as_polygon_from_points(points);
face.color = Some(Color(self.color()));

face
let segments = poly_chain.to_segments();
assert!(
segments.len() > 0,
"Attempted to compute a Brep from an empty sketch"
);

let exterior = {
let mut cycle = PartialCycle::default();
cycle.surface = Partial::from(surface);
let mut line_segments = vec![];
let mut arcs = vec![];
poly_chain.to_segments().into_iter().for_each(
|fj::SketchSegment { endpoint, route }| {
let endpoint = Point::from(endpoint);
match route {
fj::SketchSegmentRoute::Direct => {
line_segments.push(
cycle
.add_half_edge_from_point_to_start(
endpoint,
),
);
}
fj::SketchSegmentRoute::Arc { angle } => {
arcs.push((
cycle
.add_half_edge_from_point_to_start(
endpoint,
),
angle,
));
}
}
},
);
line_segments.into_iter().for_each(|mut half_edge| {
half_edge.write().update_as_line_segment()
});
arcs.into_iter().for_each(|(mut half_edge, angle)| {
half_edge.write().update_as_arc(angle.rad())
});
Partial::from_partial(cycle)
};

PartialFace {
exterior,
color: Some(Color(self.color())),
..Default::default()
}
}
};

Expand All @@ -85,14 +122,36 @@ impl Shape for fj::Sketch {
min: Point::from([-circle.radius(), -circle.radius(), 0.0]),
max: Point::from([circle.radius(), circle.radius(), 0.0]),
},
fj::Chain::PolyChain(poly_chain) => Aabb::<3>::from_points(
poly_chain
.to_segments()
.into_iter()
.map(|fj::SketchSegment::LineTo { point }| point)
.map(Point::from)
.map(Point::to_xyz),
),
fj::Chain::PolyChain(poly_chain) => {
let segments = poly_chain.to_segments();
assert!(
segments.len() > 0,
"Attempted to compute a bounding box from an empty sketch"
);

let mut points = vec![];

let mut start_point = segments[segments.len() - 1].endpoint;
segments.iter().for_each(|segment| {
match segment.route {
fj::SketchSegmentRoute::Direct => (),
fj::SketchSegmentRoute::Arc { angle } => {
use std::f64::consts::PI;
let arc_circle_data = fj_math::ArcCircleData::from_endpoints_and_angle(start_point, segment.endpoint, fj_math::Scalar::from_f64(angle.rad()));
for circle_minmax_angle in [0., PI/2., PI, 3.*PI/2.] {
let mm_angle = fj_math::Scalar::from_f64(circle_minmax_angle);
if arc_circle_data.start_angle < mm_angle && mm_angle < arc_circle_data.end_angle {
points.push(arc_circle_data.center + [arc_circle_data.radius * circle_minmax_angle.cos(), arc_circle_data.radius * circle_minmax_angle.sin()]);
}
}
},
}
points.push(Point::from(segment.endpoint));
start_point = segment.endpoint;
});

Aabb::<3>::from_points(points.into_iter().map(Point::to_xyz))
}
}
}
}
Loading

0 comments on commit c27a339

Please sign in to comment.