diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d2bbe6cc..febeb3f0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@
- `ContentConfiguration.AllowRotatation`
- `AdaptiveGrid.Clone`
- `AdditionalProperties` to ContentConfiguration.
+- `Line.Projected(Line line)`
### Fixed
diff --git a/Elements/src/Geometry/Line.cs b/Elements/src/Geometry/Line.cs
index 068292ef5..41538c8d4 100644
--- a/Elements/src/Geometry/Line.cs
+++ b/Elements/src/Geometry/Line.cs
@@ -1214,7 +1214,7 @@ public Line MergedCollinearLine(Line line)
///
/// Projects current line onto a plane
///
- /// Plane to project
+ /// Plane to project on
/// New line on a plane
public Line Projected(Plane plane)
{
@@ -1223,6 +1223,21 @@ public Line Projected(Plane plane)
return new Line(start, end);
}
+ ///
+ /// Projects current line onto another line
+ ///
+ /// Line to project on
+ /// New line on a line
+ public Line Projected(Line line)
+ {
+ var lineDirection = line.Direction();
+
+ var newLineStart = Start.Project(line.Start, lineDirection);
+ var newLineEnd = End.Project(line.End, lineDirection);
+
+ return new Line(newLineStart, newLineEnd);
+ }
+
///
/// Return an approximate fit line through a set of points using the least squares method.
///
diff --git a/Elements/src/Geometry/Vector3.cs b/Elements/src/Geometry/Vector3.cs
index 051c2faf9..c0198b79b 100644
--- a/Elements/src/Geometry/Vector3.cs
+++ b/Elements/src/Geometry/Vector3.cs
@@ -280,6 +280,16 @@ public double Dot(double x, double y, double z)
return x * this.X + y * this.Y + z * this.Z;
}
+ ///
+ /// Scales the vector by a given scalar value.
+ ///
+ /// The scalar value to multiply each component by.
+ /// A new vector where each component is scaled by the given scalar.
+ public Vector3 Scale(double scalar)
+ {
+ return new Vector3(X * scalar, Y * scalar, Z * scalar);
+ }
+
///
/// The angle in degrees from this vector to the provided vector.
/// Note that for angles in the plane that can be greater than 180 degrees,
@@ -395,10 +405,33 @@ public double DistanceTo(Ray ray)
{
return double.PositiveInfinity;
}
- var closestPointOnRay = ray.Origin + t * ray.Direction;
+ var closestPointOnRay = Project(ray);
return closestPointOnRay.DistanceTo(this);
}
+ ///
+ /// Project a point onto a ray.
+ /// The ray is treated as being infinitely long.
+ ///
+ /// The target ray.
+ public Vector3 Project(Ray ray)
+ {
+ var toPoint = this - ray.Origin;
+ var projectionLength = toPoint.Dot(ray.Direction);
+ return ray.Origin + ray.Direction.Scale(projectionLength);
+ }
+
+ ///
+ /// Project a point onto a constructed ray.
+ /// The ray is treated as being infinitely long.
+ ///
+ /// The origin of the line.
+ /// The direction of the line.
+ public Vector3 Project(Vector3 origin, Vector3 direction)
+ {
+ return Project(new Ray(origin, direction));
+ }
+
internal double ProjectedParameterOn(Ray ray)
{
return ray.Direction.Dot(this - ray.Origin) / ray.Direction.Length(); // t will be [0,1]
@@ -992,7 +1025,7 @@ public static bool AreApproximatelyEqual(IEnumerable points, double tol
// within tolerance of each other. If all points are within
// tolerance/2 of some point, then they must all be within tolerance
// of each other.
- return points.All(p => p.IsAlmostEqualTo(average, tolerance / 2.0));
+ return points.All(p => p.IsAlmostEqualTo(average, tolerance / 2.0));
}
///
diff --git a/Elements/test/LineTests.cs b/Elements/test/LineTests.cs
index 507c7b61c..8f1917b8f 100644
--- a/Elements/test/LineTests.cs
+++ b/Elements/test/LineTests.cs
@@ -141,7 +141,7 @@ public void IntersectsQuick()
public void IntersectsCircle()
{
Circle c = new Circle(new Vector3(5, 5, 5), 5);
-
+
// Intersects circle at one point and touches at other.
Line l = new Line(new Vector3(0, 5, 5), new Vector3(15, 5, 5));
Assert.True(l.Intersects(c, out var results));
@@ -601,6 +601,72 @@ public void HashCodesForDifferentComponentsAreNotEqual()
Assert.NotEqual(l1.GetHashCode(), l2.GetHashCode());
}
+ [Fact]
+ public void ProjectedLine()
+ {
+ // Identical Line Projection
+ var line = new Line(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
+ var result = line.Projected(line);
+
+ Assert.Equal(line.Start, result.Start);
+ Assert.Equal(line.End, result.End);
+
+ // Parallel Line Projection
+ var lineA = new Line(new Vector3(0, 0, 0), new Vector3(1, 0, 0));
+ var parallelLine = new Line(new Vector3(1, 0, 0), new Vector3(2, 0, 0));
+ var resultA = parallelLine.Projected(lineA);
+
+ var expectedStartA = new Vector3(1, 0, 0);
+ var expectedEndA = new Vector3(2, 0, 0);
+
+ Assert.Equal(expectedStartA, resultA.Start);
+ Assert.Equal(expectedEndA, resultA.End);
+
+ // Diagnol Line Projection
+ var lineB = new Line(new Vector3(0, 0, 0), new Vector3(1, 1, 0));
+ var diagonalLine = new Line(new Vector3(1, 1, 1), new Vector3(2, 2, 1));
+ var resultB = diagonalLine.Projected(lineB);
+
+ var expectedStartB = new Vector3(1, 1, 0);
+ var expectedEndB = new Vector3(2, 2, 0);
+
+ Assert.Equal(expectedStartB, resultB.Start);
+ Assert.Equal(expectedEndB, resultB.End);
+
+ // Perpendicular Line Projection
+ var lineC = new Line(new Vector3(0, 0, 0), new Vector3(1, 0, 0));
+ var perpendicularLine = new Line(new Vector3(0, 1, 0), new Vector3(1, 1, 0));
+ var resultC = perpendicularLine.Projected(lineC);
+
+ var expectedStartC = new Vector3(0, 0, 0);
+ var expectedEndC = new Vector3(1, 0, 0);
+
+ Assert.Equal(expectedStartC, resultC.Start);
+ Assert.Equal(expectedEndC, resultC.End);
+
+ // Negative Line Projection
+ var lineD = new Line(new Vector3(-1, -1, -1), new Vector3(-2, -2, -2));
+ var otherLineD = new Line(new Vector3(-3, -3, -3), new Vector3(-4, -4, -4));
+ var resultD = otherLineD.Projected(lineD);
+
+ var expectedStartD = new Vector3(-3, -3, -3);
+ var expectedEndD = new Vector3(-4, -4, -4);
+
+ Assert.Equal(expectedStartD, resultD.Start);
+ Assert.Equal(expectedEndD, resultD.End);
+
+ // Arbitrary Line Projection
+ var lineE = new Line(new Vector3(0, 0, 0), new Vector3(1, 2, 3));
+ var otherLineE = new Line(new Vector3(1, 1, 1), new Vector3(2, 3, 4));
+ var resultE = otherLineE.Projected(lineE);
+
+ var expectedStartE = new Vector3(0.42857, 0.85714, 1.28571); // approximate expected values
+ var expectedEndE = new Vector3(1.42857, 2.85714, 4.28571); // approximate expected values
+
+ Assert.True(resultE.Start.IsAlmostEqualTo(expectedStartE));
+ Assert.True(resultE.End.IsAlmostEqualTo(expectedEndE));
+ }
+
[Fact]
public void FitLineAndCollinearity()
{
@@ -1209,7 +1275,7 @@ public void LineDistancePointsOnSkewLines()
Assert.Equal(delta.Length(), (new Line(pt12, pt11)).DistanceTo(new Line(pt21, pt22)), 12);
Assert.Equal(delta.Length(), (new Line(pt12, pt11)).DistanceTo(new Line(pt22, pt21)), 12);
//The segments (pt12, pt13) and (pt21, pt22) does not intersect.
- //The shortest distance is from an endpoint to another segment - difference between lines plus between endpoints.
+ //The shortest distance is from an endpoint to another segment - difference between lines plus between endpoints.
var expected = (q12 * v1).DistanceTo(new Line(delta + q21 * v2, delta + q22 * v2));
Assert.Equal(expected, (new Line(pt12, pt13)).DistanceTo(new Line(pt21, pt22)), 12);
Assert.Equal(expected, (new Line(pt12, pt13)).DistanceTo(new Line(pt22, pt21)), 12);