Robochameleon  v1.0
intersections.m
Go to the documentation of this file.
1 
2 function [x0,y0,iout,jout] = intersections(x1,y1,x2,y2,robust)
3 % Theory of operation:
4 %
5 % Given two line segments, L1 and L2,
6 %
7 % L1 endpoints: (x1(1),y1(1)) and (x1(2),y1(2))
8 % L2 endpoints: (x2(1),y2(1)) and (x2(2),y2(2))
9 %
10 % we can write four equations with four unknowns and then solve them. The
11 % four unknowns are t1, t2, x0 and y0, where (x0,y0) is the intersection of
12 % L1 and L2, t1 is the distance from the starting point of L1 to the
13 % intersection relative to the length of L1 and t2 is the distance from the
14 % starting point of L2 to the intersection relative to the length of L2.
15 %
16 % So, the four equations are
17 %
18 % (x1(2) - x1(1))*t1 = x0 - x1(1)
19 % (x2(2) - x2(1))*t2 = x0 - x2(1)
20 % (y1(2) - y1(1))*t1 = y0 - y1(1)
21 % (y2(2) - y2(1))*t2 = y0 - y2(1)
22 %
23 % Rearranging and writing in matrix form,
24 %
25 % [x1(2)-x1(1) 0 -1 0; [t1; [-x1(1);
26 % 0 x2(2)-x2(1) -1 0; * t2; = -x2(1);
27 % y1(2)-y1(1) 0 0 -1; x0; -y1(1);
28 % 0 y2(2)-y2(1) 0 -1] y0] -y2(1)]
29 %
30 % Let's call that A*T = B. We can solve for T with T = A\B.
31 %
32 % Once we have our solution we just have to look at t1 and t2 to determine
33 % whether L1 and L2 intersect. If 0 <= t1 < 1 and 0 <= t2 < 1 then the two
34 % line segments cross and we can include (x0,y0) in the output.
35 %
36 % In principle, we have to perform this computation on every pair of line
37 % segments in the input data. This can be quite a large number of pairs so
38 % we will reduce it by doing a simple preliminary check to eliminate line
39 % segment pairs that could not possibly cross. The check is to look at the
40 % smallest enclosing rectangles (with sides parallel to the axes) for each
41 % line segment pair and see if they overlap. If they do then we have to
42 % compute t1 and t2 (via the A\B computation) to see if the line segments
43 % cross, but if they don't then the line segments cannot cross. In a
44 % typical application, this technique will eliminate most of the potential
45 % line segment pairs.
46 
47 % Input checks.
48 narginchk(2,5)
49 
50 % Adjustments when fewer than five arguments are supplied.
51 switch nargin
52  case 2
53  robust = true;
54  x2 = x1;
55  y2 = y1;
56  self_intersect = true;
57  case 3
58  robust = x2;
59  x2 = x1;
60  y2 = y1;
61  self_intersect = true;
62  case 4
63  robust = true;
64  self_intersect = false;
65  case 5
66  self_intersect = false;
67 end
68 
69 % x1 and y1 must be vectors with same number of points (at least 2).
70 if sum(size(x1) > 1) ~= 1 || sum(size(y1) > 1) ~= 1 || ...
71  length(x1) ~= length(y1)
72  error('X1 and Y1 must be equal-length vectors of at least 2 points.')
73 end
74 % x2 and y2 must be vectors with same number of points (at least 2).
75 if sum(size(x2) > 1) ~= 1 || sum(size(y2) > 1) ~= 1 || ...
76  length(x2) ~= length(y2)
77  error('X2 and Y2 must be equal-length vectors of at least 2 points.')
78 end
79 
80 
81 % Force all inputs to be column vectors.
82 x1 = x1(:);
83 y1 = y1(:);
84 x2 = x2(:);
85 y2 = y2(:);
86 
87 % Compute number of line segments in each curve and some differences we'll
88 % need later.
89 n1 = length(x1) - 1;
90 n2 = length(x2) - 1;
91 xy1 = [x1 y1];
92 xy2 = [x2 y2];
93 dxy1 = diff(xy1);
94 dxy2 = diff(xy2);
95 
96 % Determine the combinations of i and j where the rectangle enclosing the
97 % i'th line segment of curve 1 overlaps with the rectangle enclosing the
98 % j'th line segment of curve 2.
99 [i,j] = find(repmat(min(x1(1:end-1),x1(2:end)),1,n2) <= ...
100  repmat(max(x2(1:end-1),x2(2:end)).',n1,1) & ...
101  repmat(max(x1(1:end-1),x1(2:end)),1,n2) >= ...
102  repmat(min(x2(1:end-1),x2(2:end)).',n1,1) & ...
103  repmat(min(y1(1:end-1),y1(2:end)),1,n2) <= ...
104  repmat(max(y2(1:end-1),y2(2:end)).',n1,1) & ...
105  repmat(max(y1(1:end-1),y1(2:end)),1,n2) >= ...
106  repmat(min(y2(1:end-1),y2(2:end)).',n1,1));
107 
108 % Force i and j to be column vectors, even when their length is zero, i.e.,
109 % we want them to be 0-by-1 instead of 0-by-0.
110 i = reshape(i,[],1);
111 j = reshape(j,[],1);
112 
113 % Find segments pairs which have at least one vertex = NaN and remove them.
114 % This line is a fast way of finding such segment pairs. We take
115 % advantage of the fact that NaNs propagate through calculations, in
116 % particular subtraction (in the calculation of dxy1 and dxy2, which we
117 % need anyway) and addition.
118 % At the same time we can remove redundant combinations of i and j in the
119 % case of finding intersections of a line with itself.
120 if self_intersect
121  remove = isnan(sum(dxy1(i,:) + dxy2(j,:),2)) | j <= i + 1;
122 else
123  remove = isnan(sum(dxy1(i,:) + dxy2(j,:),2));
124 end
125 i(remove) = [];
126 j(remove) = [];
127 
128 % Initialize matrices. We'll put the T's and B's in matrices and use them
129 % one column at a time. AA is a 3-D extension of A where we'll use one
130 % plane at a time.
131 n = length(i);
132 T = zeros(4,n);
133 AA = zeros(4,4,n);
134 AA([1 2],3,:) = -1;
135 AA([3 4],4,:) = -1;
136 AA([1 3],1,:) = dxy1(i,:).';
137 AA([2 4],2,:) = dxy2(j,:).';
138 B = -[x1(i) x2(j) y1(i) y2(j)].';
139 
140 % Loop through possibilities. Trap singularity warning and then use
141 % lastwarn to see if that plane of AA is near singular. Process any such
142 % segment pairs to determine if they are colinear (overlap) or merely
143 % parallel. That test consists of checking to see if one of the endpoints
144 % of the curve 2 segment lies on the curve 1 segment. This is done by
145 % checking the cross product
146 %
147 % (x1(2),y1(2)) - (x1(1),y1(1)) x (x2(2),y2(2)) - (x1(1),y1(1)).
148 %
149 % If this is close to zero then the segments overlap.
150 
151 % If the robust option is false then we assume no two segment pairs are
152 % parallel and just go ahead and do the computation. If A is ever singular
153 % a warning will appear. This is faster and obviously you should use it
154 % only when you know you will never have overlapping or parallel segment
155 % pairs.
156 
157 if robust
158  overlap = false(n,1);
159  warning_state = warning('off','MATLAB:singularMatrix');
160  % Use try-catch to guarantee original warning state is restored.
161  try
162  lastwarn('')
163  for k = 1:n
164  T(:,k) = AA(:,:,k)\B(:,k);
165  [~,last_warn] = lastwarn;
166  lastwarn('')
167  if strcmp(last_warn,'MATLAB:singularMatrix')
168  % Force in_range(k) to be false.
169  T(1,k) = NaN;
170  % Determine if these segments overlap or are just parallel.
171  overlap(k) = rcond([dxy1(i(k),:);xy2(j(k),:) - xy1(i(k),:)]) < eps;
172  end
173  end
174  warning(warning_state)
175  catch err
176  warning(warning_state)
177  rethrow(err)
178  end
179  % Find where t1 and t2 are between 0 and 1 and return the corresponding
180  % x0 and y0 values.
181  in_range = (T(1,:) >= 0 & T(2,:) >= 0 & T(1,:) <= 1 & T(2,:) <= 1).';
182  % For overlapping segment pairs the algorithm will return an
183  % intersection point that is at the center of the overlapping region.
184  if any(overlap)
185  ia = i(overlap);
186  ja = j(overlap);
187  % set x0 and y0 to middle of overlapping region.
188  T(3,overlap) = (max(min(x1(ia),x1(ia+1)),min(x2(ja),x2(ja+1))) + ...
189  min(max(x1(ia),x1(ia+1)),max(x2(ja),x2(ja+1)))).'/2;
190  T(4,overlap) = (max(min(y1(ia),y1(ia+1)),min(y2(ja),y2(ja+1))) + ...
191  min(max(y1(ia),y1(ia+1)),max(y2(ja),y2(ja+1)))).'/2;
192  selected = in_range | overlap;
193  else
194  selected = in_range;
195  end
196  xy0 = T(3:4,selected).';
197 
198  % Remove duplicate intersection points.
199  [xy0,index] = unique(xy0,'rows');
200  x0 = xy0(:,1);
201  y0 = xy0(:,2);
202 
203  % Compute how far along each line segment the intersections are.
204  if nargout > 2
205  sel_index = find(selected);
206  sel = sel_index(index);
207  iout = i(sel) + T(1,sel).';
208  jout = j(sel) + T(2,sel).';
209  end
210 else % non-robust option
211  for k = 1:n
212  [L,U] = lu(AA(:,:,k));
213  T(:,k) = U\(L\B(:,k));
214  end
215 
216  % Find where t1 and t2 are between 0 and 1 and return the corresponding
217  % x0 and y0 values.
218  in_range = (T(1,:) >= 0 & T(2,:) >= 0 & T(1,:) < 1 & T(2,:) < 1).';
219  x0 = T(3,in_range).';
220  y0 = T(4,in_range).';
221 
222  % Compute how far along each line segment the intersections are.
223  if nargout > 2
224  iout = i(in_range) + T(1,in_range).';
225  jout = j(in_range) + T(2,in_range).';
226  end
227 end
228 
229 % Plot the results (useful for debugging).
230 % plot(x1,y1,x2,y2,x0,y0,'ok');
function intersections(in x1, in y1, in x2, in y2, in robust)
Intersections of curves.