Skip to content

Commit 7309fc7

Browse files
committed
Line implements Interface IHasArcLength
1 parent 6952333 commit 7309fc7

File tree

6 files changed

+128
-35
lines changed

6 files changed

+128
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
- `Arc` is now parameterized 0->2Pi
3434
- `Line` now inherits from `TrimmedCurve<InfiniteLine>`.
3535
- `Line` is now parameterized 0->length.
36+
- `Line` now implements the `IHasArcLength` interface
3637
- `Bezier` now inherits from `BoundedCurve`.
3738
- `Polyline` is now parameterized 0->length.
3839
- `Circle` is now parameterized 0->2Pi.

Elements/src/Elements.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<Summary>The Elements library provides object types for generating the built environment.</Summary>
1111
<PackageProjectUrl>https://github.com/hypar-io/elements</PackageProjectUrl>
1212
<RepositoryUrl>https://github.com/hypar-io/elements</RepositoryUrl>
13-
<Version>$(Version)</Version>
13+
<Version>21.21.21</Version>
1414
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
1515
</PropertyGroup>
1616
<ItemGroup>

Elements/src/Geometry/Line.cs

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using Newtonsoft.Json;
66
using Elements.Spatial;
7+
using Elements.Geometry.Interfaces;
78

89
namespace Elements.Geometry
910
{
@@ -15,7 +16,7 @@ namespace Elements.Geometry
1516
/// [!code-csharp[Main](../../Elements/test/LineTests.cs?name=example)]
1617
/// </example>
1718
/// TODO: Rename this class to LineSegment
18-
public class Line : TrimmedCurve<InfiniteLine>, IEquatable<Line>
19+
public class Line : TrimmedCurve<InfiniteLine>, IEquatable<Line>, IHasArcLength
1920
{
2021
/// <summary>
2122
/// The domain of the curve.
@@ -123,6 +124,48 @@ public override Vector3 PointAt(double u)
123124
return this.BasisCurve.PointAt(u);
124125
}
125126

127+
/// <summary>
128+
/// The mid point of the curve.
129+
/// </summary>
130+
/// <returns>The length based midpoint.</returns>
131+
public virtual Vector3 MidPoint()
132+
{
133+
return PointAtNormalizedLength(0.5);
134+
}
135+
136+
/// <summary>
137+
/// Returns the point on the line corresponding to the specified length value.
138+
/// </summary>
139+
/// <param name="length">The length value along the line.</param>
140+
/// <returns>The point on the line corresponding to the specified length value.</returns>
141+
/// <exception cref="ArgumentException">Thrown when the specified length is out of range.</exception>
142+
public virtual Vector3 PointAtLength(double length)
143+
{
144+
double totalLength = ArcLength(this.Domain.Min, this.Domain.Max); // Calculate the total length of the Line
145+
146+
if (length < 0 || length > totalLength)
147+
{
148+
throw new ArgumentException("The specified length is out of range.");
149+
}
150+
var lengthParameter = length / totalLength;
151+
return this.PointAtNormalized(lengthParameter);
152+
}
153+
154+
/// <summary>
155+
/// Returns the point on the line corresponding to the specified normalized length-based parameter value.
156+
/// </summary>
157+
/// <param name="parameter">The normalized length-based parameter value, ranging from 0 to 1.</param>
158+
/// <returns>The point on the line corresponding to the specified normalized length-based parameter value.</returns>
159+
/// <exception cref="ArgumentException">Thrown when the specified parameter is out of range.</exception>
160+
public virtual Vector3 PointAtNormalizedLength(double parameter)
161+
{
162+
if (parameter < 0 || parameter > 1)
163+
{
164+
throw new ArgumentException("The specified parameter is out of range.");
165+
}
166+
return PointAtLength(parameter * this.ArcLength(this.Domain.Min, this.Domain.Max));
167+
}
168+
126169
/// <inheritdoc/>
127170
public override Curve Transformed(Transform transform)
128171
{
@@ -541,38 +584,79 @@ public static bool PointOnLine(Vector3 point, Vector3 start, Vector3 end, bool i
541584
return false;
542585
}
543586

587+
// /// <summary>
588+
// /// Divide the line into as many segments of the provided length as possible.
589+
// /// </summary>
590+
// /// <param name="l">The length.</param>
591+
// /// <param name="removeShortSegments">A flag indicating whether segments shorter than l should be removed.</param>
592+
// public List<Line> DivideByLength(double l, bool removeShortSegments = false)
593+
// {
594+
// var len = this.Length();
595+
// if (l > len)
596+
// {
597+
// return new List<Line>() { new Line(this.Start, this.End) };
598+
// }
599+
600+
// var total = 0.0;
601+
// var d = this.Direction();
602+
// var lines = new List<Line>();
603+
// while (total + l <= len)
604+
// {
605+
// var a = this.Start + d * total;
606+
// var b = a + d * l;
607+
// lines.Add(new Line(a, b));
608+
// total += l;
609+
// }
610+
// if (total < len && !removeShortSegments)
611+
// {
612+
// var a = this.Start + d * total;
613+
// if (!a.IsAlmostEqualTo(End))
614+
// {
615+
// lines.Add(new Line(a, End));
616+
// }
617+
// }
618+
// return lines;
619+
// }
620+
544621
/// <summary>
545-
/// Divide the line into as many segments of the provided length as possible.
622+
/// Divides the line into segments of the specified length.
546623
/// </summary>
547-
/// <param name="l">The length.</param>
548-
/// <param name="removeShortSegments">A flag indicating whether segments shorter than l should be removed.</param>
549-
public List<Line> DivideByLength(double l, bool removeShortSegments = false)
624+
/// <param name="divisionLength">The desired length of each segment.</param>
625+
/// <returns>A list of points representing the segments.</returns>
626+
public Vector3[] DivideByLength(double divisionLength)
550627
{
551-
var len = this.Length();
552-
if (l > len)
628+
var segments = new List<Vector3>();
629+
630+
if (this.ArcLength(this.Domain.Min, this.Domain.Max) < double.Epsilon)
553631
{
554-
return new List<Line>() { new Line(this.Start, this.End) };
632+
// Handle invalid line with insufficient length
633+
return new Vector3[0];
555634
}
556635

557-
var total = 0.0;
558-
var d = this.Direction();
559-
var lines = new List<Line>();
560-
while (total + l <= len)
636+
var currentProgression = 0.0;
637+
segments = new List<Vector3> { this.Start };
638+
639+
// currentProgression from last segment before hitting end
640+
if (currentProgression != 0.0)
561641
{
562-
var a = this.Start + d * total;
563-
var b = a + d * l;
564-
lines.Add(new Line(a, b));
565-
total += l;
642+
currentProgression -= divisionLength;
566643
}
567-
if (total < len && !removeShortSegments)
644+
while (this.ArcLength(this.Domain.Min, this.Domain.Max) >= currentProgression + divisionLength)
568645
{
569-
var a = this.Start + d * total;
570-
if (!a.IsAlmostEqualTo(End))
571-
{
572-
lines.Add(new Line(a, End));
573-
}
646+
segments.Add(this.PointAt(currentProgression + divisionLength));
647+
currentProgression += divisionLength;
574648
}
575-
return lines;
649+
// Set currentProgression from divisionLength less distance from last segment point
650+
currentProgression = divisionLength - segments.LastOrDefault().DistanceTo(this.End);
651+
652+
// Add the last vertex of the polyline as the endpoint of the last segment if it
653+
// is not already part of the list
654+
if (!segments.LastOrDefault().IsAlmostEqualTo(this.End))
655+
{
656+
segments.Add(this.End);
657+
}
658+
659+
return segments.ToArray();
576660
}
577661

578662
/// <summary>
@@ -925,7 +1009,7 @@ public double DistanceTo(Line other)
9251009
// line vectors are not collinear, their directions share the common plane.
9261010
else
9271011
{
928-
// dStartStart length is distance to the common plane.
1012+
// dStartStart length is distance to the common plane.
9291013
dStartStart = dStartStart.ProjectOnto(cross);
9301014
Vector3 vStartStart = other.Start + dStartStart - this.Start;
9311015
Vector3 vStartEnd = other.Start + dStartStart - this.End;
@@ -1028,8 +1112,8 @@ public List<Line> Trim(Polygon polygon, out List<Line> outsideSegments, bool inc
10281112
var B = intersectionsOrdered[i + 1];
10291113
if (A.IsAlmostEqualTo(B)) // skip duplicate points
10301114
{
1031-
// it's possible that A is outside, but B is at an edge, even
1032-
// if they are within tolerance of each other.
1115+
// it's possible that A is outside, but B is at an edge, even
1116+
// if they are within tolerance of each other.
10331117
// This can happen due to floating point error when the point is almost exactly
10341118
// epsilon distance from the edge.
10351119
// so if we have duplicate points, we have to update the containment value.

Elements/src/Geometry/Polyline.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ public virtual Line[] Segments()
8585
return SegmentsInternal(this.Vertices);
8686
}
8787

88-
89-
9088
/// <summary>
9189
/// The mid point of the curve.
9290
/// </summary>

Elements/test/LineTests.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ public void DivideIntoEqualSegmentsSingle()
163163
public void DivideByLength()
164164
{
165165
var l = new Line(Vector3.Origin, new Vector3(5, 0));
166-
var segments = l.DivideByLength(1.1, true);
167-
Assert.Equal(4, segments.Count);
166+
var segments = l.DivideByLength(1.1);
167+
Assert.Equal(6, segments.Count());
168168

169-
var segments1 = l.DivideByLength(1.1);
170-
Assert.Equal(5, segments1.Count);
169+
var segments1 = l.DivideByLength(2);
170+
Assert.Equal(4, segments1.Count());
171171
}
172172

173173
[Fact]
@@ -650,6 +650,17 @@ public void GetParameterAt()
650650
var expectedVector = line.PointAt(uValue);
651651
Assert.InRange(uValue, 0, line.Length());
652652
Assert.True(vector.IsAlmostEqualTo(expectedVector));
653+
654+
var parameter = 0.5;
655+
var testParameterMidpoint = line.PointAtNormalizedLength(parameter);
656+
Assert.True(testParameterMidpoint.IsAlmostEqualTo(middle));
657+
658+
var midlength = line.Length() * parameter;
659+
var testLengthMidpoint = line.PointAtLength(midlength);
660+
Assert.True(testLengthMidpoint.IsAlmostEqualTo(testParameterMidpoint));
661+
662+
var midpoint = line.MidPoint();
663+
Assert.True(midpoint.IsAlmostEqualTo(testLengthMidpoint));
653664
}
654665

655666
[Theory]
@@ -1110,7 +1121,7 @@ public void LineDistancePointsOnSkewLines()
11101121
Assert.Equal(delta.Length(), (new Line(pt12, pt11)).DistanceTo(new Line(pt21, pt22)), 12);
11111122
Assert.Equal(delta.Length(), (new Line(pt12, pt11)).DistanceTo(new Line(pt22, pt21)), 12);
11121123
//The segments (pt12, pt13) and (pt21, pt22) does not intersect.
1113-
//The shortest distance is from an endpoint to another segment - difference between lines plus between endpoints.
1124+
//The shortest distance is from an endpoint to another segment - difference between lines plus between endpoints.
11141125
var expected = (q12 * v1).DistanceTo(new Line(delta + q21 * v2, delta + q22 * v2));
11151126
Assert.Equal(expected, (new Line(pt12, pt13)).DistanceTo(new Line(pt21, pt22)), 12);
11161127
Assert.Equal(expected, (new Line(pt12, pt13)).DistanceTo(new Line(pt22, pt21)), 12);

Elements/test/PolylineTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ public void GetParameterAt()
270270
var parameter = 0.5;
271271
var testMidpoint = new Vector3(5.0, 14.560525, 0); // Midpoint
272272
var testParameterMidpoint = polyline.PointAtNormalizedLength(parameter);
273-
var t = testParameterMidpoint.IsAlmostEqualTo(testMidpoint);
274273
Assert.True(testParameterMidpoint.IsAlmostEqualTo(testMidpoint));
275274

276275
var midlength = polyline.Length() * parameter;

0 commit comments

Comments
 (0)