1

How can we tell Mathematica to gives us a set of non-intersecting lines? In this case two lines intersect if they have a point (not an endpoint) in common. Consider this simple case:

l1 = {{-1, 0}, {1, 0}};
l2 = {{0, -1}, {0, 1}};
lines = {l1, l2};

The idea is to create a function which, given a set a lines, returns a set of non-intersecting lines. If such function exists say split then the output of

split[lines]

would be

{
 {{-1, 0}, {0,0}},
 {{ 0, 0}, {1,0}}, 
 {{ 0,-1}, {0,0}}, 
 {{ 0, 0}, {0,1}}
}

The function detected that {0,0} is the intersection between the two lines in the set and in order to have non-intersecting lines it broke the line segments at the intersections thus generating 2 more lines. This process gets more complicated if the original input contains more lines. Does anyone know how to do this efficiently in Mathematica without using loops? It might help to know an algorithm to find if two lines are intersecting.

Note

This question is the second part of my attempt to find out how to make wire frames in Mathematica with hidden line removal. Please feel free to add more appropriate tags.

Community
  • 1
  • 1
jmlopez
  • 4,853
  • 4
  • 40
  • 74
  • I don't have Mma here, but what you want is to use standard linear algebra to represent each line as A.{x, y}=c, and find the point where the equations for both lines are true, using LinearSolve. Then, check that the solution is in between the ends of the two line segments given. If so, break up the lines at that point. As with my answer to your earlier question, you want to do this to Tuples[Sort[lines],{2}]. – Verbeia Jun 16 '11 at 08:25
  • @Verbeia, What is `Tuples[Sort[lines], {2}]` supposed to do? lets assum that `lines` is defined as in my post. – jmlopez Jun 16 '11 at 08:58
  • the Tuples function is only necessary if you have a list of more than two lines that you are checking, and you want to check all possible pairs of lines. All it does is create a list of all possible pairs of elements from a list with more than two elements. ACL's answer below deals with the issue of duplicates, and might be a better solution than Tuples. – Verbeia Jun 16 '11 at 11:54

2 Answers2

3

if you assume that split exists, you then need to apply it to all pairs; these may be produced by

ClearAll[permsnodups];
permsnodups[lp_] := DeleteDuplicates[Permutations[lp, {2}],
   ((#1[[1]] == #2[[1]]) &&(#1[[2]] == #2[[2]]) || 
   (#1[[1]] == #2[[2]]) && (#1[[2]] == #2[[1]])) &]

which does this: permsnodups[{a, b, c, d}] gives {{a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d}}, over which you could map your split function (ie these are all pairs, making sure that if {a,b} is there then {b,a} is not since then you are doing twice the work for no reason--it's like doing $\sum_{i,j>i}$ as opposed to $\sum_{i,j}$).

EDIT: Here is an implementation of split (I was stuck with no internet access for half an hour or so, so worked out the relevant equations by hand, and this is not based on the link you gave nor is it optimized or pretty):

ClearAll[split2]
split2[{{ai_, bi_}, {ci_, di_}}] := Module[
{g1, g2, a, b, c, d, x0, y0, alpha, beta},
(*make sure that a is to the left of b*)

If[ai[[1]] > bi[[1]], {a, b} = {bi, ai}, {a, b} = {ai, bi}];
If[ci[[1]] > di[[1]], {c, d} = {di, ci}, {c, d} = {ci, di}];
g1 = (b[[2]] - a[[2]])/(b[[1]] - a[[1]]);
g2 = (d[[2]] - c[[2]])/(d[[1]] - c[[1]]);
If[g2 \[Equal] g1,
    {{a, b}, {c, d}},(*they're parallel*)

alpha = a[[2]] - g1*a[[1]];
    beta = c[[2]] - g2*c[[1]];
    x0 = (alpha - beta)/(g2 - g1);(*intersection x*)

If[(a[[1]] < x0 < b[[1]]) && (c[[1]] < x0 < 
   d[[1]]),(*they do intersect*)
            y0 = alpha + g1*x0;
            {{a, #}, {#, b}, {c, #}, {#, d}} &@{x0, y0},
            {{a, b}, {c, d}}(*they don't intersect after all*)]]]

(in fact it's atrociously slow and ugly). Anyway, you can see that it works like this:

Manipulate[
Grid[{{Graphics[{Line[{p1, p2}, VertexColors \[Rule] {Red, Green}], 
  Line[{p3, p4}]},
        PlotRange \[Rule] 3, Axes \[Rule] True],
        (*Reap@split2[{{p1,p2},{p3,p4}}]//Last,*)
        If[
            Length@split2[{{p1, p2}, {p3, p4}}] \[Equal] 2,
            "not intersecting",
            "intersecting"]}}],
{{p1, {0, 1}}, Locator}, {{p2, {1, 1}}, Locator},
{{p3, {2.3, -.1}}, Locator}, {{p4, {2, 1}}, Locator}]

which produces things like

enter image description here

and

enter image description here

(you can move the locators around). Mind you, my split2 divides by zero whenever one of the lines is vertical (this can be fixed but I haven't done it).

In any case this is all very slow and ugly. It could be made faster by compiling and making listable (and using the link you gave), but my current coffee break is over (or was over half an hour ago). I'll try to get back to this later.

Meanwhile, do ask if there are any concrete questions (eg, if you can't see what breaks for vertical lines). And note that while this does what you ask, if you do map over a list of lines you'll end up with a ragged list which you will have to flatten. But, this is what you asked for :)

acl
  • 6,490
  • 1
  • 27
  • 33
  • Could you do a quick implementation of `split` for completeness? I'm not very familiar with the mapping functions in Mathematica. I'm sorry to ask but if you edit your answer with the split function could you apply it to an example with 3 intersecting lines. Say: `l1 = {{-1, 0}, {1, 0}}; l2 = {{0, -1}, {0, 1}}; l3 = {{-1,1},{1,1}}; lines = {l1, l2, l3}`. – jmlopez Jun 16 '11 at 09:57
  • you did answer the question but this has brought another one. I will think about it some more and post it in an edit since there is no point in starting another one. – jmlopez Jun 17 '11 at 02:10
2

For determining the intersection, you can also do the following parametric approach, that does not suffer from the usual problems of methods involving the cartesian equations (ie division by zero ...):

f[t_, l_List] := l[[1]] + t (l[[2]] - l[[1]])
split[l1_, l2_] := Module[{s},
  If[(s = ToRules@
       Reduce[f[t1, l1]==f[t2, l2] && 0 <t2< 1 && 0 <t1< 1, {t1,t2},Reals]) =={},
   Return[{l1, l2}],
   Return[{{f[0, l1], f[t1, l1] /. s}, {f[1, l1], f[t1, l1] /. s},
           {f[0, l2], f[t2, l2] /. s}, {f[1, l2], f[t2, l2] /. s}}]
   ]]
Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190
  • Thank you belisarius. This is a very nice way of splitting lines at the intersection. I'm a little concerned about efficiency. This function will have to be executed quite a lot. In any case, I think I can work something out with your code and the one provided by acl. I will edit my question once I have a more solid case. – jmlopez Jun 17 '11 at 01:59
  • @jmlopez in my poor's man laptop the approx performance is 8 secs per 10K intersections – Dr. belisarius Jun 17 '11 at 02:25
  • @belisarius I don't think `Reduce` can be compiled, though. The one I gave is clumsy but at least can be compiled to C automatically (after a bit of massaging) – acl Jun 17 '11 at 09:30
  • @acl Mine tried to be a subtle way to say that your `split2[{{{1, 0}, {-1, 0}}, {{0, 1}, {0, -1}}}]` fails :) – Dr. belisarius Jun 17 '11 at 10:02
  • @acl I am sure that almost anything is better than `Reduce` from the performance POV :) – Dr. belisarius Jun 17 '11 at 10:04
  • @belisarius yes, it divides by zero :) nothing that can't be fixed by a couple of extra Ifs (which can also be compiled) :) – acl Jun 17 '11 at 10:06
  • @belisarius well I should have checked my old schoolbooks: the method given in the link in the question would just need a single If to check for division by zero, and is compilable too... Oh well. – acl Jun 17 '11 at 10:08