Merge pull request #2275 from hannobraun/geometry

Read half-edge geometry from geometry layer in most places
This commit is contained in:
Hanno Braun 2024-03-20 14:41:39 +01:00 committed by GitHub
commit ee15b609a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 117 additions and 58 deletions

View File

@ -2,8 +2,6 @@
//! //!
//! See [`CycleApprox`]. //! See [`CycleApprox`].
use std::ops::Deref;
use fj_math::Segment; use fj_math::Segment;
use crate::{geometry::SurfaceGeometry, objects::Cycle, Core}; use crate::{geometry::SurfaceGeometry, objects::Cycle, Core};
@ -30,8 +28,7 @@ impl Approx for (&Cycle, &SurfaceGeometry) {
.half_edges() .half_edges()
.iter() .iter()
.map(|half_edge| { .map(|half_edge| {
(half_edge.deref(), surface) (half_edge, surface).approx_with_cache(tolerance, cache, core)
.approx_with_cache(tolerance, cache, core)
}) })
.collect(); .collect();

View File

@ -5,14 +5,16 @@
//! approximations are usually used to build cycle approximations, and this way, //! approximations are usually used to build cycle approximations, and this way,
//! the caller doesn't have to deal with duplicate vertices. //! the caller doesn't have to deal with duplicate vertices.
use crate::{geometry::SurfaceGeometry, objects::HalfEdge, Core}; use crate::{
geometry::SurfaceGeometry, objects::HalfEdge, storage::Handle, Core,
};
use super::{ use super::{
curve::CurveApproxCache, vertex::VertexApproxCache, Approx, ApproxPoint, curve::CurveApproxCache, vertex::VertexApproxCache, Approx, ApproxPoint,
Tolerance, Tolerance,
}; };
impl Approx for (&HalfEdge, &SurfaceGeometry) { impl Approx for (&Handle<HalfEdge>, &SurfaceGeometry) {
type Approximation = HalfEdgeApprox; type Approximation = HalfEdgeApprox;
type Cache = HalfEdgeApproxCache; type Cache = HalfEdgeApproxCache;
@ -44,7 +46,7 @@ impl Approx for (&HalfEdge, &SurfaceGeometry) {
let rest = { let rest = {
let approx = ( let approx = (
half_edge.curve(), half_edge.curve(),
half_edge.path(), core.layers.geometry.of_half_edge(half_edge).path,
surface, surface,
half_edge.boundary(), half_edge.boundary(),
) )
@ -55,8 +57,12 @@ impl Approx for (&HalfEdge, &SurfaceGeometry) {
); );
approx.points.into_iter().map(|point| { approx.points.into_iter().map(|point| {
let point_surface = let point_surface = core
half_edge.path().point_from_path_coords(point.local_form); .layers
.geometry
.of_half_edge(half_edge)
.path
.point_from_path_coords(point.local_form);
ApproxPoint::new(point_surface, point.global_form) ApproxPoint::new(point_surface, point.global_form)
}) })

View File

@ -110,7 +110,7 @@ impl Approx for &Face {
exterior, exterior,
interiors, interiors,
color: self.region().get_color(core), color: self.region().get_color(core),
coord_handedness: self.coord_handedness(), coord_handedness: self.coord_handedness(&core.layers.geometry),
} }
} }
} }

View File

@ -3,11 +3,12 @@ use fj_math::{Aabb, Vector};
use crate::{ use crate::{
geometry::{Geometry, SurfacePath}, geometry::{Geometry, SurfacePath},
objects::HalfEdge, objects::HalfEdge,
storage::Handle,
}; };
impl super::BoundingVolume<2> for HalfEdge { impl super::BoundingVolume<2> for Handle<HalfEdge> {
fn aabb(&self, _: &Geometry) -> Option<Aabb<2>> { fn aabb(&self, geometry: &Geometry) -> Option<Aabb<2>> {
match self.path() { match geometry.of_half_edge(self).path {
SurfacePath::Circle(circle) => { SurfacePath::Circle(circle) => {
// Just calculate the AABB of the whole circle. This is not the // Just calculate the AABB of the whole circle. This is not the
// most precise, but it should do for now. // most precise, but it should do for now.
@ -22,7 +23,10 @@ impl super::BoundingVolume<2> for HalfEdge {
} }
SurfacePath::Line(_) => { SurfacePath::Line(_) => {
let points = self.boundary().inner.map(|point_curve| { let points = self.boundary().inner.map(|point_curve| {
self.path().point_from_path_coords(point_curve) geometry
.of_half_edge(self)
.path
.point_from_path_coords(point_curve)
}); });
Some(Aabb::<2>::from_points(points)) Some(Aabb::<2>::from_points(points))

View File

@ -1,7 +1,7 @@
use fj_math::{Scalar, Winding}; use fj_math::{Scalar, Winding};
use crate::{ use crate::{
geometry::SurfacePath, geometry::{Geometry, SurfacePath},
objects::{HalfEdge, ObjectSet}, objects::{HalfEdge, ObjectSet},
storage::Handle, storage::Handle,
}; };
@ -29,7 +29,7 @@ impl Cycle {
/// Please note that this is not *the* winding of the cycle, only one of the /// Please note that this is not *the* winding of the cycle, only one of the
/// two possible windings, depending on the direction you look at the /// two possible windings, depending on the direction you look at the
/// surface that the cycle is defined on from. /// surface that the cycle is defined on from.
pub fn winding(&self) -> Winding { pub fn winding(&self, geometry: &Geometry) -> Winding {
// The cycle could be made up of one or two circles. If that is the // The cycle could be made up of one or two circles. If that is the
// case, the winding of the cycle is determined by the winding of the // case, the winding of the cycle is determined by the winding of the
// first circle. // first circle.
@ -43,7 +43,7 @@ impl Cycle {
let [a, b] = first.boundary().inner; let [a, b] = first.boundary().inner;
let edge_direction_positive = a < b; let edge_direction_positive = a < b;
let circle = match first.path() { let circle = match geometry.of_half_edge(first).path {
SurfacePath::Circle(circle) => circle, SurfacePath::Circle(circle) => circle,
SurfacePath::Line(_) => unreachable!( SurfacePath::Line(_) => unreachable!(
"Invalid cycle: less than 3 edges, but not all are circles" "Invalid cycle: less than 3 edges, but not all are circles"

View File

@ -1,6 +1,7 @@
use fj_math::Winding; use fj_math::Winding;
use crate::{ use crate::{
geometry::Geometry,
objects::{Region, Surface}, objects::{Region, Surface},
storage::{Handle, HandleWrapper}, storage::{Handle, HandleWrapper},
}; };
@ -64,8 +65,8 @@ impl Face {
/// Faces *do* have an orientation, meaning they have definite front and /// Faces *do* have an orientation, meaning they have definite front and
/// back sides. The front side is the side, where the face's exterior cycle /// back sides. The front side is the side, where the face's exterior cycle
/// is wound counter-clockwise. /// is wound counter-clockwise.
pub fn coord_handedness(&self) -> Handedness { pub fn coord_handedness(&self, geometry: &Geometry) -> Handedness {
match self.region.exterior().winding() { match self.region.exterior().winding(geometry) {
Winding::Ccw => Handedness::RightHanded, Winding::Ccw => Handedness::RightHanded,
Winding::Cw => Handedness::LeftHanded, Winding::Cw => Handedness::LeftHanded,
} }

View File

@ -34,7 +34,7 @@ pub trait BuildHalfEdge {
core: &mut Core, core: &mut Core,
) -> Handle<HalfEdge> { ) -> Handle<HalfEdge> {
let half_edge = HalfEdge::new( let half_edge = HalfEdge::new(
sibling.path(), core.layers.geometry.of_half_edge(sibling).path,
sibling.boundary().reverse(), sibling.boundary().reverse(),
sibling.curve().clone(), sibling.curve().clone(),
start_vertex, start_vertex,
@ -44,7 +44,7 @@ pub trait BuildHalfEdge {
core.layers.geometry.define_half_edge( core.layers.geometry.define_half_edge(
half_edge.clone(), half_edge.clone(),
HalfEdgeGeometry { HalfEdgeGeometry {
path: sibling.path(), path: core.layers.geometry.of_half_edge(sibling).path,
}, },
); );

View File

@ -50,7 +50,7 @@ impl UpdateHalfEdgeGeometry for Handle<HalfEdge> {
update: impl FnOnce(SurfacePath) -> SurfacePath, update: impl FnOnce(SurfacePath) -> SurfacePath,
core: &mut Core, core: &mut Core,
) -> Self { ) -> Self {
let path = update(self.path()); let path = update(core.layers.geometry.of_half_edge(self).path);
let half_edge = HalfEdge::new( let half_edge = HalfEdge::new(
path, path,
@ -73,7 +73,7 @@ impl UpdateHalfEdgeGeometry for Handle<HalfEdge> {
core: &mut Core, core: &mut Core,
) -> Self { ) -> Self {
HalfEdge::new( HalfEdge::new(
self.path(), core.layers.geometry.of_half_edge(self).path,
update(self.boundary()), update(self.boundary()),
self.curve().clone(), self.curve().clone(),
self.start_vertex().clone(), self.start_vertex().clone(),

View File

@ -68,7 +68,10 @@ impl AddHole for Shell {
[Cycle::empty().add_joined_edges( [Cycle::empty().add_joined_edges(
[( [(
entry.clone(), entry.clone(),
entry.path(), core.layers
.geometry
.of_half_edge(&entry)
.path,
entry.boundary(), entry.boundary(),
)], )],
core, core,
@ -139,7 +142,10 @@ impl AddHole for Shell {
[Cycle::empty().add_joined_edges( [Cycle::empty().add_joined_edges(
[( [(
entry.clone(), entry.clone(),
entry.path(), core.layers
.geometry
.of_half_edge(&entry)
.path,
entry.boundary(), entry.boundary(),
)], )],
core, core,
@ -159,7 +165,14 @@ impl AddHole for Shell {
|region, core| { |region, core| {
region.add_interiors( region.add_interiors(
[Cycle::empty().add_joined_edges( [Cycle::empty().add_joined_edges(
[(exit.clone(), exit.path(), exit.boundary())], [(
exit.clone(),
core.layers
.geometry
.of_half_edge(exit)
.path,
exit.boundary(),
)],
core, core,
)], )],
core, core,

View File

@ -14,7 +14,7 @@ impl Reverse for Cycle {
.pairs() .pairs()
.map(|(current, next)| { .map(|(current, next)| {
let half_edge = HalfEdge::new( let half_edge = HalfEdge::new(
current.path(), core.layers.geometry.of_half_edge(current).path,
current.boundary().reverse(), current.boundary().reverse(),
current.curve().clone(), current.curve().clone(),
next.start_vertex().clone(), next.start_vertex().clone(),
@ -25,7 +25,7 @@ impl Reverse for Cycle {
core.layers.geometry.define_half_edge( core.layers.geometry.define_half_edge(
half_edge.clone(), half_edge.clone(),
HalfEdgeGeometry { HalfEdgeGeometry {
path: current.path(), path: core.layers.geometry.of_half_edge(current).path,
}, },
); );

View File

@ -10,7 +10,7 @@ use super::ReverseCurveCoordinateSystems;
impl ReverseCurveCoordinateSystems for Handle<HalfEdge> { impl ReverseCurveCoordinateSystems for Handle<HalfEdge> {
fn reverse_curve_coordinate_systems(&self, core: &mut Core) -> Self { fn reverse_curve_coordinate_systems(&self, core: &mut Core) -> Self {
let path = self.path().reverse(); let path = core.layers.geometry.of_half_edge(self).path.reverse();
let boundary = self.boundary().reverse(); let boundary = self.boundary().reverse();
let half_edge = HalfEdge::new( let half_edge = HalfEdge::new(

View File

@ -33,7 +33,7 @@ pub trait SplitHalfEdge {
) -> [Handle<HalfEdge>; 2]; ) -> [Handle<HalfEdge>; 2];
} }
impl SplitHalfEdge for HalfEdge { impl SplitHalfEdge for Handle<HalfEdge> {
fn split_half_edge( fn split_half_edge(
&self, &self,
point: impl Into<Point<1>>, point: impl Into<Point<1>>,
@ -44,14 +44,14 @@ impl SplitHalfEdge for HalfEdge {
let [start, end] = self.boundary().inner; let [start, end] = self.boundary().inner;
let a = HalfEdge::new( let a = HalfEdge::new(
self.path(), core.layers.geometry.of_half_edge(self).path,
[start, point], [start, point],
self.curve().clone(), self.curve().clone(),
self.start_vertex().clone(), self.start_vertex().clone(),
) )
.insert(core); .insert(core);
let b = HalfEdge::new( let b = HalfEdge::new(
self.path(), core.layers.geometry.of_half_edge(self).path,
[point, end], [point, end],
self.curve().clone(), self.curve().clone(),
Vertex::new().insert(core), Vertex::new().insert(core),
@ -60,11 +60,15 @@ impl SplitHalfEdge for HalfEdge {
core.layers.geometry.define_half_edge( core.layers.geometry.define_half_edge(
a.clone(), a.clone(),
HalfEdgeGeometry { path: self.path() }, HalfEdgeGeometry {
path: core.layers.geometry.of_half_edge(self).path,
},
); );
core.layers.geometry.define_half_edge( core.layers.geometry.define_half_edge(
b.clone(), b.clone(),
HalfEdgeGeometry { path: self.path() }, HalfEdgeGeometry {
path: core.layers.geometry.of_half_edge(self).path,
},
); );
[a, b] [a, b]

View File

@ -77,7 +77,7 @@ impl SweepCycle for Cycle {
top_edges.push(( top_edges.push((
top_edge, top_edge,
bottom_half_edge.path(), core.layers.geometry.of_half_edge(bottom_half_edge).path,
bottom_half_edge.boundary(), bottom_half_edge.boundary(),
)); ));
} }

View File

@ -47,7 +47,7 @@ pub trait SweepHalfEdge {
) -> (Face, Handle<HalfEdge>); ) -> (Face, Handle<HalfEdge>);
} }
impl SweepHalfEdge for HalfEdge { impl SweepHalfEdge for Handle<HalfEdge> {
fn sweep_half_edge( fn sweep_half_edge(
&self, &self,
end_vertex: Handle<Vertex>, end_vertex: Handle<Vertex>,
@ -59,7 +59,12 @@ impl SweepHalfEdge for HalfEdge {
) -> (Face, Handle<HalfEdge>) { ) -> (Face, Handle<HalfEdge>) {
let path = path.into(); let path = path.into();
let surface = self.path().sweep_surface_path(surface, path, core); let surface = core
.layers
.geometry
.of_half_edge(self)
.path
.sweep_surface_path(surface, path, core);
// Next, we need to define the boundaries of the face. Let's start with // Next, we need to define the boundaries of the face. Let's start with
// the global vertices and edges. // the global vertices and edges.

View File

@ -40,7 +40,10 @@ impl SweepSketch for Sketch {
let region = { let region = {
// The following code assumes that the sketch is winded counter- // The following code assumes that the sketch is winded counter-
// clockwise. Let's check that real quick. // clockwise. Let's check that real quick.
assert!(region.exterior().winding().is_ccw()); assert!(region
.exterior()
.winding(&core.layers.geometry)
.is_ccw());
let is_negative_sweep = { let is_negative_sweep = {
let u = match core.layers.geometry.of_surface(&surface).u { let u = match core.layers.geometry.of_surface(&surface).u {

View File

@ -16,7 +16,7 @@ impl TransformObject for Handle<HalfEdge> {
) -> Self { ) -> Self {
// Don't need to transform the path, as that's defined in surface // Don't need to transform the path, as that's defined in surface
// coordinates. // coordinates.
let path = self.path(); let path = core.layers.geometry.of_half_edge(self).path;
let boundary = self.boundary(); let boundary = self.boundary();
let curve = self let curve = self
.curve() .curve()

View File

@ -13,10 +13,10 @@ impl Validate for Face {
&self, &self,
_: &ValidationConfig, _: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
_: &Geometry, geometry: &Geometry,
) { ) {
FaceValidationError::check_boundary(self, errors); FaceValidationError::check_boundary(self, errors);
FaceValidationError::check_interior_winding(self, errors); FaceValidationError::check_interior_winding(self, geometry, errors);
} }
} }
@ -56,14 +56,18 @@ impl FaceValidationError {
// checks for `Cycle` to make sure that the cycle is closed properly. // checks for `Cycle` to make sure that the cycle is closed properly.
} }
fn check_interior_winding(face: &Face, errors: &mut Vec<ValidationError>) { fn check_interior_winding(
face: &Face,
geometry: &Geometry,
errors: &mut Vec<ValidationError>,
) {
if face.region().exterior().half_edges().is_empty() { if face.region().exterior().half_edges().is_empty() {
// Can't determine winding, if the cycle has no edges. Sounds like a // Can't determine winding, if the cycle has no edges. Sounds like a
// job for a different validation check. // job for a different validation check.
return; return;
} }
let exterior_winding = face.region().exterior().winding(); let exterior_winding = face.region().exterior().winding(geometry);
for interior in face.region().interiors() { for interior in face.region().interiors() {
if interior.half_edges().is_empty() { if interior.half_edges().is_empty() {
@ -71,7 +75,7 @@ impl FaceValidationError {
// like a job for a different validation check. // like a job for a different validation check.
continue; continue;
} }
let interior_winding = interior.winding(); let interior_winding = interior.winding(geometry);
if exterior_winding == interior_winding { if exterior_winding == interior_winding {
errors.push( errors.push(

View File

@ -104,6 +104,7 @@ impl ShellValidationError {
surface_a: &SurfaceGeometry, surface_a: &SurfaceGeometry,
edge_b: &Handle<HalfEdge>, edge_b: &Handle<HalfEdge>,
surface_b: &SurfaceGeometry, surface_b: &SurfaceGeometry,
geometry: &Geometry,
config: &ValidationConfig, config: &ValidationConfig,
mismatches: &mut Vec<CurveCoordinateSystemMismatch>, mismatches: &mut Vec<CurveCoordinateSystemMismatch>,
) { ) {
@ -116,10 +117,14 @@ impl ShellValidationError {
let c = a + (d - a) * 2. / 3.; let c = a + (d - a) * 2. / 3.;
for point_curve in [a, b, c, d] { for point_curve in [a, b, c, d] {
let a_surface = let a_surface = geometry
edge_a.path().point_from_path_coords(point_curve); .of_half_edge(edge_a)
let b_surface = .path
edge_b.path().point_from_path_coords(point_curve); .point_from_path_coords(point_curve);
let b_surface = geometry
.of_half_edge(edge_b)
.path
.point_from_path_coords(point_curve);
let a_global = let a_global =
surface_a.point_from_surface_coords(a_surface); surface_a.point_from_surface_coords(a_surface);
@ -148,6 +153,7 @@ impl ShellValidationError {
&geometry.of_surface(surface_a), &geometry.of_surface(surface_a),
edge_b, edge_b,
&geometry.of_surface(surface_b), &geometry.of_surface(surface_b),
geometry,
config, config,
&mut mismatches, &mut mismatches,
); );
@ -156,6 +162,7 @@ impl ShellValidationError {
&geometry.of_surface(surface_b), &geometry.of_surface(surface_b),
edge_a, edge_a,
&geometry.of_surface(surface_a), &geometry.of_surface(surface_a),
geometry,
config, config,
&mut mismatches, &mut mismatches,
); );
@ -245,6 +252,7 @@ impl ShellValidationError {
&geometry.of_surface(surface_a), &geometry.of_surface(surface_a),
half_edge_b.clone(), half_edge_b.clone(),
&geometry.of_surface(surface_b), &geometry.of_surface(surface_b),
geometry,
) )
.all(|d| d < config.distinct_min_distance) .all(|d| d < config.distinct_min_distance)
{ {
@ -369,14 +377,19 @@ fn distances(
surface_a: &SurfaceGeometry, surface_a: &SurfaceGeometry,
edge_b: Handle<HalfEdge>, edge_b: Handle<HalfEdge>,
surface_b: &SurfaceGeometry, surface_b: &SurfaceGeometry,
geometry: &Geometry,
) -> impl Iterator<Item = Scalar> { ) -> impl Iterator<Item = Scalar> {
fn sample( fn sample(
percent: f64, percent: f64,
(edge, surface): (&Handle<HalfEdge>, &SurfaceGeometry), (edge, surface): (&Handle<HalfEdge>, &SurfaceGeometry),
geometry: &Geometry,
) -> Point<3> { ) -> Point<3> {
let [start, end] = edge.boundary().inner; let [start, end] = edge.boundary().inner;
let path_coords = start + (end - start) * percent; let path_coords = start + (end - start) * percent;
let surface_coords = edge.path().point_from_path_coords(path_coords); let surface_coords = geometry
.of_half_edge(edge)
.path
.point_from_path_coords(path_coords);
surface.point_from_surface_coords(surface_coords) surface.point_from_surface_coords(surface_coords)
} }
@ -389,8 +402,8 @@ fn distances(
let mut distances = Vec::new(); let mut distances = Vec::new();
for i in 0..sample_count { for i in 0..sample_count {
let percent = i as f64 * step; let percent = i as f64 * step;
let sample1 = sample(percent, (&edge_a, surface_a)); let sample1 = sample(percent, (&edge_a, surface_a), geometry);
let sample2 = sample(1.0 - percent, (&edge_b, surface_b)); let sample2 = sample(1.0 - percent, (&edge_b, surface_b), geometry);
distances.push(sample1.distance_to(&sample2)) distances.push(sample1.distance_to(&sample2))
} }
distances.into_iter() distances.into_iter()

View File

@ -13,11 +13,15 @@ impl Validate for Sketch {
&self, &self,
config: &ValidationConfig, config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
_: &Geometry, geometry: &Geometry,
) { ) {
SketchValidationError::check_object_references(self, config, errors); SketchValidationError::check_object_references(self, config, errors);
SketchValidationError::check_exterior_cycles(self, config, errors); SketchValidationError::check_exterior_cycles(
SketchValidationError::check_interior_cycles(self, config, errors); self, geometry, config, errors,
);
SketchValidationError::check_interior_cycles(
self, geometry, config, errors,
);
} }
} }
@ -74,12 +78,13 @@ impl SketchValidationError {
fn check_exterior_cycles( fn check_exterior_cycles(
sketch: &Sketch, sketch: &Sketch,
geometry: &Geometry,
_config: &ValidationConfig, _config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
sketch.regions().iter().for_each(|region| { sketch.regions().iter().for_each(|region| {
let cycle = region.exterior(); let cycle = region.exterior();
if cycle.winding() == Winding::Cw { if cycle.winding(geometry) == Winding::Cw {
errors.push(ValidationError::Sketch( errors.push(ValidationError::Sketch(
SketchValidationError::ClockwiseExteriorCycle { SketchValidationError::ClockwiseExteriorCycle {
cycle: cycle.clone(), cycle: cycle.clone(),
@ -91,6 +96,7 @@ impl SketchValidationError {
fn check_interior_cycles( fn check_interior_cycles(
sketch: &Sketch, sketch: &Sketch,
geometry: &Geometry,
_config: &ValidationConfig, _config: &ValidationConfig,
errors: &mut Vec<ValidationError>, errors: &mut Vec<ValidationError>,
) { ) {
@ -98,7 +104,7 @@ impl SketchValidationError {
region region
.interiors() .interiors()
.iter() .iter()
.filter(|interior| interior.winding() == Winding::Ccw) .filter(|interior| interior.winding(geometry) == Winding::Ccw)
.for_each(|cycle| { .for_each(|cycle| {
errors.push(ValidationError::Sketch( errors.push(ValidationError::Sketch(
SketchValidationError::CounterClockwiseInteriorCycle { SketchValidationError::CounterClockwiseInteriorCycle {

View File

@ -41,13 +41,16 @@ pub struct AdjacentHalfEdgesNotConnected {
impl ValidationCheck<Cycle> for AdjacentHalfEdgesNotConnected { impl ValidationCheck<Cycle> for AdjacentHalfEdgesNotConnected {
fn check( fn check(
object: &Cycle, object: &Cycle,
_: &Geometry, geometry: &Geometry,
config: &ValidationConfig, config: &ValidationConfig,
) -> impl Iterator<Item = Self> { ) -> impl Iterator<Item = Self> {
object.half_edges().pairs().filter_map(|(first, second)| { object.half_edges().pairs().filter_map(|(first, second)| {
let end_pos_of_first_half_edge = { let end_pos_of_first_half_edge = {
let [_, end] = first.boundary().inner; let [_, end] = first.boundary().inner;
first.path().point_from_path_coords(end) geometry
.of_half_edge(first)
.path
.point_from_path_coords(end)
}; };
let start_pos_of_second_half_edge = second.start_position(); let start_pos_of_second_half_edge = second.start_position();