From bee86d8d770a99eb36bdbbf73de1faf23675e394 Mon Sep 17 00:00:00 2001 From: James Bradley Date: Wed, 29 May 2024 15:03:57 -0400 Subject: [PATCH 1/4] project line orthogonally onto another --- Elements/src/Geometry/Line.cs | 25 ++++++++++++++++++++++++- Elements/src/Geometry/Vector3.cs | 12 +++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Elements/src/Geometry/Line.cs b/Elements/src/Geometry/Line.cs index 068292ef5..642fdfbb4 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,29 @@ public Line Projected(Plane plane) return new Line(start, end); } + /// + /// Projects current line orthogonally onto another line + /// + /// Line to project on + /// New line on a line + public Line Projected(Line line) + { + var lineDirection = line.Direction(); + var normalizedDirection = new Vector3(lineDirection.X, lineDirection.Y, lineDirection.Z); + + Vector3 ProjectPoint(Vector3 point, Vector3 lineStart) + { + var toPoint = point - lineStart; + var projectionLength = toPoint.Dot(normalizedDirection); + return lineStart + normalizedDirection.Scale(projectionLength); + } + + var newLineStart = ProjectPoint(Start, line.Start); + var newLineEnd = ProjectPoint(End, line.Start); + + 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..ed9c5677d 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, @@ -992,7 +1002,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)); } /// From 5338d14af4eb9fec717de85a77c3fbf2c970d208 Mon Sep 17 00:00:00 2001 From: James Bradley Date: Wed, 29 May 2024 15:08:38 -0400 Subject: [PATCH 2/4] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From d20b740c737cf0545e101d0214f4fa6c622cd5dd Mon Sep 17 00:00:00 2001 From: James Bradley Date: Thu, 30 May 2024 15:04:58 -0400 Subject: [PATCH 3/4] add line projection tests --- Elements/src/Geometry/Line.cs | 5 +-- Elements/test/LineTests.cs | 70 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/Elements/src/Geometry/Line.cs b/Elements/src/Geometry/Line.cs index 642fdfbb4..7e34639be 100644 --- a/Elements/src/Geometry/Line.cs +++ b/Elements/src/Geometry/Line.cs @@ -1231,13 +1231,12 @@ public Line Projected(Plane plane) public Line Projected(Line line) { var lineDirection = line.Direction(); - var normalizedDirection = new Vector3(lineDirection.X, lineDirection.Y, lineDirection.Z); Vector3 ProjectPoint(Vector3 point, Vector3 lineStart) { var toPoint = point - lineStart; - var projectionLength = toPoint.Dot(normalizedDirection); - return lineStart + normalizedDirection.Scale(projectionLength); + var projectionLength = toPoint.Dot(lineDirection); + return lineStart + lineDirection.Scale(projectionLength); } var newLineStart = ProjectPoint(Start, line.Start); 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); From 15a9948af2a0cb6abfe037ee936cff3076982201 Mon Sep 17 00:00:00 2001 From: James Bradley Date: Thu, 30 May 2024 15:26:33 -0400 Subject: [PATCH 4/4] comments --- Elements/src/Geometry/Line.cs | 13 +++---------- Elements/src/Geometry/Vector3.cs | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Elements/src/Geometry/Line.cs b/Elements/src/Geometry/Line.cs index 7e34639be..41538c8d4 100644 --- a/Elements/src/Geometry/Line.cs +++ b/Elements/src/Geometry/Line.cs @@ -1224,7 +1224,7 @@ public Line Projected(Plane plane) } /// - /// Projects current line orthogonally onto another line + /// Projects current line onto another line /// /// Line to project on /// New line on a line @@ -1232,15 +1232,8 @@ public Line Projected(Line line) { var lineDirection = line.Direction(); - Vector3 ProjectPoint(Vector3 point, Vector3 lineStart) - { - var toPoint = point - lineStart; - var projectionLength = toPoint.Dot(lineDirection); - return lineStart + lineDirection.Scale(projectionLength); - } - - var newLineStart = ProjectPoint(Start, line.Start); - var newLineEnd = ProjectPoint(End, line.Start); + var newLineStart = Start.Project(line.Start, lineDirection); + var newLineEnd = End.Project(line.End, lineDirection); return new Line(newLineStart, newLineEnd); } diff --git a/Elements/src/Geometry/Vector3.cs b/Elements/src/Geometry/Vector3.cs index ed9c5677d..c0198b79b 100644 --- a/Elements/src/Geometry/Vector3.cs +++ b/Elements/src/Geometry/Vector3.cs @@ -405,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]