Skip to content

Commit

Permalink
Merge pull request #1086 from hypar-io/project-line
Browse files Browse the repository at this point in the history
project line orthogonally onto another
  • Loading branch information
jamesbradleym committed May 30, 2024
2 parents d246cee + 15a9948 commit 2a88a1d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
- `ContentConfiguration.AllowRotatation`
- `AdaptiveGrid.Clone`
- `AdditionalProperties` to ContentConfiguration.
- `Line.Projected(Line line)`

### Fixed

Expand Down
17 changes: 16 additions & 1 deletion Elements/src/Geometry/Line.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,7 @@ public Line MergedCollinearLine(Line line)
/// <summary>
/// Projects current line onto a plane
/// </summary>
/// <param name="plane">Plane to project</param>
/// <param name="plane">Plane to project on</param>
/// <returns>New line on a plane</returns>
public Line Projected(Plane plane)
{
Expand All @@ -1223,6 +1223,21 @@ public Line Projected(Plane plane)
return new Line(start, end);
}

/// <summary>
/// Projects current line onto another line
/// </summary>
/// <param name="line">Line to project on</param>
/// <returns>New line on a line</returns>
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);
}

/// <summary>
/// Return an approximate fit line through a set of points using the least squares method.
/// </summary>
Expand Down
37 changes: 35 additions & 2 deletions Elements/src/Geometry/Vector3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,16 @@ public double Dot(double x, double y, double z)
return x * this.X + y * this.Y + z * this.Z;
}

/// <summary>
/// Scales the vector by a given scalar value.
/// </summary>
/// <param name="scalar">The scalar value to multiply each component by.</param>
/// <returns>A new vector where each component is scaled by the given scalar.</returns>
public Vector3 Scale(double scalar)
{
return new Vector3(X * scalar, Y * scalar, Z * scalar);
}

/// <summary>
/// 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,
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Project a point onto a ray.
/// The ray is treated as being infinitely long.
/// </summary>
/// <param name="ray">The target ray.</param>
public Vector3 Project(Ray ray)
{
var toPoint = this - ray.Origin;
var projectionLength = toPoint.Dot(ray.Direction);
return ray.Origin + ray.Direction.Scale(projectionLength);
}

/// <summary>
/// Project a point onto a constructed ray.
/// The ray is treated as being infinitely long.
/// </summary>
/// <param name="origin">The origin of the line.</param>
/// <param name="direction">The direction of the line.</param>
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]
Expand Down Expand Up @@ -992,7 +1025,7 @@ public static bool AreApproximatelyEqual(IEnumerable<Vector3> 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));
}

/// <summary>
Expand Down
70 changes: 68 additions & 2 deletions Elements/test/LineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 2a88a1d

Please sign in to comment.