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);