How can parallel line segments intersect at parameter 0.5?

Hi,

I’m working on a script that iteratively checks for line-line-intersections of a set of line segments that lie within the same plane.

I’ve noticed that sometimes, two nearly parallel line segments have an intersection at a parameter very close to 0.5, which roughly translates to a mid point.

For the top line segment the intersection parameter is found at 0.5039106145251397 and for the bottom one at 0.49497206703910607.

I’m using Rhino.Geometry.Intersect.Intersection.LineLine() to compute the intersections.

I would like to understand what causes this?

I’ve currently fixed the bug that thus emerged, by computing the “two-dimensional” vector cross product of the line segment directions and checking its z-value as a scalar.
If this scalar is between the negative and positive absolute model tolerance (i.e. 0.001), I simply skip the intersection computation for near parallel lines.

This fixes the issue, but I would still want to know how this happens!

Thanks for taking a look.

parallel_intersection.gh (3.4 KB)

Are the lines very very close together, say to 6 or 7 decimal places? I noticed a few years ago when 2 polyline vertices get as close as that, but distinct, they can be treated as coincident.

In my mm-scale Rhino document they are 2.83mm apart, which I wouldn’t call “very very close together”.

I mean the document model tolerance is set to 0.001mm.

Thanks for your reply.

You’re welcome. I’m just reading the docs now, but I assume they haven’t taken the classical approach of the ancient Greeks, and defined parallel lines as intersecting at infinity, which in some unusual co-ordinate system is at (0.5, 0.5)?

1 Like

Is this an example of this situation described in the docs?:

Remarks:
If the lines are exactly parallel, meaning the system of equations used to find a and b has no numerical solution, then False is returned. If the lines are nearly parallel, which is often numerically True even if you think the lines look exactly parallel, then the closest points are found and True is returned. So, if you care about weeding out “parallel” lines, then you need to do something like the following:

bool rc = Intersect.LineLine(lineA, lineB, out a, out b, tolerance, segments);  
if (rc)  
{  
double angle_tol = RhinoMath.ToRadians(1.0); // or whatever  
double parallel_tol = Math.Cos(angle_tol);  
if ( Math.Abs(lineA.UnitTangent * lineB.UnitTangent) >= parallel_tol )  
{  
... do whatever you think is appropriate  
}  
}

Dim rc As Boolean = Intersect.LineLine(lineA, lineB, a, b, tolerance, segments)  
If (rc) Then  
Dim angle_tol As Double = RhinoMath.ToRadians(1.0) 'or whatever  
Dim parallel_tolerance As Double = Math.Cos(angle_tol)  
If (Math.Abs(lineA.UnitTangent * lineB.UnitTangent) >= parallel_tolerance) Then  
... do whatever you think is appropriate  
End If  
End If
1 Like

This statement from the documentation totally fits my case, and like mentioned above I use the vector cross product to weed out nearly parallel and parallel lines before checking for intersections. They seem to use the dot product which is another way.

I still want to know why parallel lines have an intersection near their mid points though.

1 Like

I think 0.5, 0.5 should be interpreted as what the algorithm found to be the closest pair of points to each other, with one on each line.

Description:

Find the unique closest-points pair between two infinite lines, if it exists.

In hindsight, a more accurate namespace for this function to live in could’ve been chosen than “Intersect”

1 Like

Thanks, for this explanation. That makes sense! Next time, I’m going to read the docs more prudently.

What Rhino version are using? Just tried your file in Rhino 8.18.25076.15001 and can’t seem to reproduce the isse (your code doesn’t implement the overload James is referring to, but this one), if I understood you correctly:

Edit: Uploaded file:
parallel_intersection_Anders.gh (4.6 KB)

1 Like

Hi Anders!

I’m using 8.18.25084.13002 from yesterday on macOS.

You’re right about that, but what James has explained above and what is remarked under the other constructor in the docs, makes totally sense in my case, and it is even necessary to prevent bugs, since even for these nearly parallel lines, the “return code” is True and two parameters are found.

Maybe a discrepancy between the Mac and Windows version?

Thanks for your reply.

1 Like

As James said, this is just “what the algorithm found”, which is almost purely noise for two almost parallel lines.

One reason these points could end up in the middle of your lines would be if the noise made it seem like one of them was just slightly out of plane, with one end slightly raised and one end slightly lowered relative to xOy.

Yeah that’s weird and I can reproduce the different behaviors. I’ll dig into that a little bit more.

2 Likes

Thanks for the explanation!

Btw, you may want to check first for parallel case like this:

if l1.Direction.IsParallelTo(l2.Direction) != 0:
    print("these are parallel")
    # note, 1 means parallel, -1 means anti-parallel
2 Likes

Thanks, I’ve since changed the if-statment to:
if 1.0 - tolerance <= abs(l1.UnitTangent * l2.UnitTangent) <= 1.0 + tolerance:

The reasoning is that I don’t need the normal vector that the three-dimensional cross product provides and the dot product theoretically requires 4 operations less to be calculated.

Anyway, without looking at the docs I guess the IsParallelTo method does something similar, because its return values looks suspiciously like what the vector dot product of two normal vectors spits out. :wink:

As far as I can tell, Vector3d.IsParallelTo does the same thing as the test recommended in the docs for Rhino.Geometry.Intersect.Intersection.LineLine:

double angle_tol = RhinoMath.ToRadians(1.0); // or whatever  
double parallel_tol = Math.Cos(angle_tol);  
if ( Math.Abs(lineA.UnitTangent * lineB.UnitTangent) >= parallel_tol )  
{  
// lines are almost parallel
//... do whatever you think is appropriate  
} 

i.e., compare the dot product of two unit vectors against the cosine of the angle tolerance. It’s very close to what you are doing too, but this way there is a physical value that explains the tolerance number.

There are probably a few ways to make that use the fewest number of floating point operations but I’m not sure it’s worth caring about 3 or 4 extra multiplications.

The right-hand side comparison is not needed here, if you find a dot product of two unit vectors that is bigger than 1.0 + tolerance, there’s a big bug somewhere.

1 Like

You’re totally right. Thanks for pointing that out and for all the other help provided!