diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e1cd132c..4518233ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - `Profile.ThickenedEdgePolygons` - `Elements.MEP` - `GeometricElement.RepresentationInstances` +- Custom implementations of `ToPolyline` for `IndexedPolycurve`, `Polyline` and `Polygon`. ### Fixed @@ -58,6 +59,7 @@ - `Polyline.Frames` now works correctly with `startSetbackDistance` and `endSetbackDistance` parameters. - `Polygon.Frames` now works correctly with `startSetbackDistance` and `endSetbackDistance` parameters. - `BoundedCurve.ToPolyline` now works correctly for `EllipticalArc` class. +- `Vector3.DistanceToDistanceTo(Polygon polygon, out Vector3 closestPoint)` now also checks the last edge of the polygon. ### Changed diff --git a/Elements/src/Geometry/BoundedCurve.cs b/Elements/src/Geometry/BoundedCurve.cs index c8e79b858..1fedb6de6 100644 --- a/Elements/src/Geometry/BoundedCurve.cs +++ b/Elements/src/Geometry/BoundedCurve.cs @@ -84,12 +84,21 @@ public virtual Transform[] Frames(double startSetbackDistance = 0.0, return transforms; } + /// + /// Create a polyline through a set of 10 segments along the curve. + /// + /// A polyline. + public virtual Polyline ToPolyline() + { + return ToPolyline(10); + } + /// /// Create a polyline through a set of points along the curve. /// /// The number of divisions of the curve. /// A polyline. - public virtual Polyline ToPolyline(int divisions = 10) + public virtual Polyline ToPolyline(int divisions) { var pts = new List(divisions + 1); var step = this.Domain.Length / divisions; diff --git a/Elements/src/Geometry/IndexedPolycurve.cs b/Elements/src/Geometry/IndexedPolycurve.cs index 0af0a9603..b34303275 100644 --- a/Elements/src/Geometry/IndexedPolycurve.cs +++ b/Elements/src/Geometry/IndexedPolycurve.cs @@ -666,6 +666,94 @@ internal static void CheckSelfIntersectionAndThrow(Transform t, IEnumerable<(Vec } } + /// + /// Create a polyline through curves of IndexedPolycurve, interpolating any curves that are not lines. + /// + /// A polyline. + public override Polyline ToPolyline() + { + List vertices = new List(); + foreach (var curve in _curves) + { + if (curve is Line) + { + vertices.Add(curve.Start); + } + else + { + var pl = curve.ToPolyline(); + vertices.AddRange(pl.Vertices.Take(pl.Vertices.Count - 1)); + } + } + vertices.Add(End); + return new Polyline(vertices); + } + + /// + /// Create a polyline through a set of points along the curve. + /// End points of curves are added first and then non straight curves are divided uniformly. + /// If number of divisions is less than number of curves - points are uniformly distributed + /// though whole domain, deviating heavily from original shape. + /// + /// The number of divisions of the curve. + /// This can lead to highly distorted result. + /// A polyline. + public override Polyline ToPolyline(int divisions) + { + // + if (divisions < _curves.Count) + { + return base.ToPolyline(divisions); + } + + double extraSplits = divisions - _curves.Count; + List vertices = new List(divisions + 1) { Start }; + var numArcs = _curves.Count(c => !(c is Line)); + if (numArcs > 0) + { + // If polycurve consists of lines and curves - excess divisions are uniformly distributed + // among curves as dividing the lines wont add any new details in Polyline. + foreach (var curve in _curves) + { + if (!(curve is Line)) + { + var splitsPerArc = Math.Ceiling(1.0d * extraSplits / numArcs); + var splits = Math.Min(splitsPerArc, extraSplits); + var step = curve.Domain.Length / (splitsPerArc + 1); + for (int i = 1; i <= splitsPerArc; i++) + { + var t = curve.Domain.Min + i * step; + vertices.Add(curve.PointAt(t)); + } + extraSplits -= splitsPerArc; + numArcs--; + } + vertices.Add(curve.End); + } + } + else + { + // If polycurve consists only of lines - divisions are distributed uniformly among them. + var linesLeft = _curves.Count; + foreach (var curve in _curves) + { + var splitsPerLine = Math.Ceiling(1.0d * extraSplits / linesLeft); + var splits = Math.Min(splitsPerLine, extraSplits); + var step = curve.Domain.Length / (splitsPerLine + 1); + for (int i = 1; i <= splitsPerLine; i++) + { + var t = curve.Domain.Min + i * step; + vertices.Add(curve.PointAt(t)); + } + vertices.Add(curve.End); + extraSplits -= splitsPerLine; + linesLeft--; + } + } + vertices.Add(End); + return new Polyline(vertices); + } + /// /// Get the enumerator for this indexed polycurve. /// diff --git a/Elements/src/Geometry/Polygon.cs b/Elements/src/Geometry/Polygon.cs index 1f92b8a98..da4f35794 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -1823,6 +1823,18 @@ public override Line[] Segments() return lines; } + /// + /// Create a polyline through vertices of a polygon. + /// + /// A polyline. + public override Polyline ToPolyline() + { + var vertices = new List(Vertices.Count + 1); + vertices.AddRange(Vertices); + vertices.Add(End); + return new Polyline(vertices); + } + /// /// Reverse the direction of a polygon. /// diff --git a/Elements/src/Geometry/Polyline.cs b/Elements/src/Geometry/Polyline.cs index 0af338570..cce7c09f5 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -84,6 +84,15 @@ public virtual Line[] Segments() return SegmentsInternal(this.Vertices); } + /// + /// Create a copy of a polyline. + /// + /// A polyline. + public override Polyline ToPolyline() + { + return new Polyline(Vertices.ToList()); + } + /// /// Get the transform at the specified parameter along the polyline. /// diff --git a/Elements/src/Geometry/Vector3.cs b/Elements/src/Geometry/Vector3.cs index 051c2faf9..2072a8ca6 100644 --- a/Elements/src/Geometry/Vector3.cs +++ b/Elements/src/Geometry/Vector3.cs @@ -523,7 +523,7 @@ public double DistanceTo(Polygon polygon, out Vector3 closestPoint) } else { - return this.DistanceTo(new Polyline(polygon.Vertices), out closestPoint); + return this.DistanceTo(polygon as Polyline, out closestPoint); } } diff --git a/Elements/test/IndexedPolyCurveTests.cs b/Elements/test/IndexedPolyCurveTests.cs index 2ddf1a349..093aca9f8 100644 --- a/Elements/test/IndexedPolyCurveTests.cs +++ b/Elements/test/IndexedPolyCurveTests.cs @@ -227,5 +227,64 @@ public void GetSubdivisionParameters() Assert.Equal(1.25, parameters[0]); Assert.Equal(1.75, parameters[1]); } + + [Fact] + public void ToPolyyline() + { + var arc0 = new Arc(new Vector3(2.5, 5), 2.5, 0, 180); + var arc1 = new Arc(new Vector3(-2.5, 0), 2.5, 360, 180); + var a = new Vector3(5, 0, 0); + var b = new Vector3(5, 5, 0); + var c = arc0.Mid(); + var d = new Vector3(0, 5, 0); + var e = Vector3.Origin; + var f = arc1.Mid(); + var g = new Vector3(-5, 0, 0); + var vertices = new[] { a, b, c, d, e, f, g }; + var indices = new[]{ + new[]{0,1}, + new[]{1,2,3}, + new[]{3,4}, + new[]{4,5,6} + }; + var pc = new IndexedPolycurve(vertices, indices); + + var pl = pc.ToPolyline(indices.Count()); + Assert.Equal(5, pl.Vertices.Count); + Assert.True(a.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(b.IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(d.IsAlmostEqualTo(pl.Vertices[2])); + Assert.True(e.IsAlmostEqualTo(pl.Vertices[3])); + Assert.True(g.IsAlmostEqualTo(pl.Vertices.Last())); + + pl = pc.ToPolyline(); + Assert.Equal(arc0.ToPolyline().Vertices.Count + arc1.ToPolyline().Vertices.Count + 1, + pl.Vertices.Count); + Assert.True(a.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(b.IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(g.IsAlmostEqualTo(pl.Vertices.Last())); + + pl = pc.ToPolyline(9); + Assert.Equal(10, pl.Vertices.Count); + Assert.True(a.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(b.IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(arc0.PointAtNormalized(0.25).IsAlmostEqualTo(pl.Vertices[2])); + Assert.True(arc0.PointAtNormalized(0.50).IsAlmostEqualTo(pl.Vertices[3])); + Assert.True(arc0.PointAtNormalized(0.75).IsAlmostEqualTo(pl.Vertices[4])); + Assert.True(d.IsAlmostEqualTo(pl.Vertices[5])); + Assert.True(e.IsAlmostEqualTo(pl.Vertices[6])); + Assert.True(arc1.PointAtNormalized(1.0 / 3).IsAlmostEqualTo(pl.Vertices[7])); + Assert.True(arc1.PointAtNormalized(2.0 / 3).IsAlmostEqualTo(pl.Vertices[8])); + Assert.True(g.IsAlmostEqualTo(pl.Vertices[9])); + + pl = pc.ToPolyline(5); + Assert.Equal(6, pl.Vertices.Count); + Assert.True(a.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(b.IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(arc0.PointAtNormalized(0.50).IsAlmostEqualTo(pl.Vertices[2])); + Assert.True(d.IsAlmostEqualTo(pl.Vertices[3])); + Assert.True(e.IsAlmostEqualTo(pl.Vertices[4])); + Assert.True(g.IsAlmostEqualTo(pl.Vertices[5])); + } } } \ No newline at end of file diff --git a/Elements/test/PolygonTests.cs b/Elements/test/PolygonTests.cs index 959199c26..c3f5ab3c9 100644 --- a/Elements/test/PolygonTests.cs +++ b/Elements/test/PolygonTests.cs @@ -2391,5 +2391,46 @@ public void Frames() Assert.Equal((1, 2), frames[3].Origin); Assert.True(Vector3.XAxis.IsParallelTo(frames[3].ZAxis)); } + + [Fact] + public void ToPolyline() + { + var L = Polygon.L(10, 15, 5); + var pl = L.ToPolyline(); + Assert.Equal(L.Vertices.Count + 1, pl.Vertices.Count); + for (int i = 0; i < L.Vertices.Count; i++) + { + Assert.True(L.Vertices[i].IsAlmostEqualTo(pl.Vertices[i])); + } + Assert.True(pl.Vertices.Last().IsAlmostEqualTo(L.End)); + + pl = L.ToPolyline(4); + var segments = L.Segments(); + Assert.Equal(5, pl.Vertices.Count); + Assert.True(L.Start.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(segments[1].Mid().IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(L.Vertices[3].IsAlmostEqualTo(pl.Vertices[2])); + Assert.True(segments[4].Mid().IsAlmostEqualTo(pl.Vertices[3])); + Assert.True(L.End.IsAlmostEqualTo(pl.Vertices[4])); + + pl = L.ToPolyline(13); + segments = L.Segments(); + Assert.Equal(pl.Length(), L.Length()); + Assert.Equal(14, pl.Vertices.Count); + Assert.True(L.Start.IsAlmostEqualTo(pl.Vertices[0])); + Assert.True(segments[0].PointAtNormalized(1.0 / 3).IsAlmostEqualTo(pl.Vertices[1])); + Assert.True(segments[0].PointAtNormalized(2.0 / 3).IsAlmostEqualTo(pl.Vertices[2])); + Assert.True(segments[1].Start.IsAlmostEqualTo(pl.Vertices[3])); + Assert.True(segments[1].Mid().IsAlmostEqualTo(pl.Vertices[4])); + Assert.True(segments[2].Start.IsAlmostEqualTo(pl.Vertices[5])); + Assert.True(segments[2].Mid().IsAlmostEqualTo(pl.Vertices[6])); + Assert.True(segments[3].Start.IsAlmostEqualTo(pl.Vertices[7])); + Assert.True(segments[3].Mid().IsAlmostEqualTo(pl.Vertices[8])); + Assert.True(segments[4].Start.IsAlmostEqualTo(pl.Vertices[9])); + Assert.True(segments[4].Mid().IsAlmostEqualTo(pl.Vertices[10])); + Assert.True(segments[5].Start.IsAlmostEqualTo(pl.Vertices[11])); + Assert.True(segments[5].Mid().IsAlmostEqualTo(pl.Vertices[12])); + Assert.True(L.End.IsAlmostEqualTo(pl.Vertices[13])); + } } } \ No newline at end of file diff --git a/Elements/test/VectorTests.cs b/Elements/test/VectorTests.cs index 44957050e..70fa7a585 100644 --- a/Elements/test/VectorTests.cs +++ b/Elements/test/VectorTests.cs @@ -595,6 +595,19 @@ public void UniqueAverageWithinTolerance() x => x.IsAlmostEqualTo(new Vector3(5, 5))); } + [Fact] + public void DistanceToL() + { + var L = Polygon.L(10, 15, 5); + var position = new Vector3(-1, 3); + var distance = position.DistanceTo(L.ToPolyline(), out var closestA); + var distancePolygon = position.DistanceTo(L, out var closestB); + Assert.Equal(1, distance); + Assert.Equal(1, distancePolygon); + Assert.Equal(new Vector3(0, 3), closestA); + Assert.Equal(new Vector3(0, 3), closestB); + } + [Fact] public void PointsAreCoplanarWithinTolerance() {