a b/SurfaceIntersection.m
1
function [intMatrix, intSurface] = SurfaceIntersection(surface1, surface2, varargin)
2
%SURFACEINTERSECTION intersection of 2 surfaces
3
% [intMatrix, intSurface] = SurfaceIntersection(surface1, surface2)
4
% calculates the intersection of surfaces 1 and 2. Code can either return
5
% just the matrix indicating which face of surface1 intersected with face
6
% of surface2, which is calculated using Tomas Moller algorithm, or can
7
% also return the actual line of intersection. In case when parts of the
8
% surface 1 and 2 lay on the same plane the intersection is a 2D area
9
% instead of 1D edge. In such a case the intersection area will be
10
% triangulated and intSurface.edges will hold the edges of the
11
% triangulation surface and intSurface.faces will hold the faces.
12
%
13
% INPUT:
14
%  * surface1 & surface2 - two surfaces defined as structs or classes.
15
%    Several inputs are possible:
16
%    - struct with "faces" and "vertices" fields
17
%    - 'triangulation' class (only the boundary surface will be used)
18
%    - 'delaunayTriangulation' class
19
%
20
% OUTPUT:
21
% * intMatrix - sparse Matrix with n1 x n2 dimension where n1 and n2 are
22
%               number of faces in surfaces
23
% * intSurface - a structure with following fields:
24
%     intSurface.vertices - N x 3 array of unique points
25
%     intSurface.edges    - N x 2 array of edge vertex ID's
26
%     intSurface.faces    - N x 3 array of face vertex ID's
27
%
28
% ALGORITHM:
29
% Based on Triangle/triangle intersection test routine by Tomas Mller, 1997.
30
%  See article "A Fast Triangle-Triangle Intersection Test",
31
%  Journal of Graphics Tools, 2(2), 1997
32
%  http://web.stanford.edu/class/cs277/resources/papers/Moller1997b.pdf
33
%  http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/opttritri.txt
34
35
%% Get FACES and VERTICES inputs
36
if isa(surface1, 'triangulation')
37
  [surface1.faces, surface1.vertices] = freeBoundary(surface1);
38
elseif isa(surface1, 'delaunayTriangulation')
39
  S = surface1;
40
  surface1 = [];
41
  surface1.faces    = S.ConnectivityList;
42
  surface1.vertices = S.Points;
43
  clear S
44
end
45
if isa(surface2, 'triangulation')
46
  [surface2.faces, surface1.vertices] = freeBoundary(surface2);
47
elseif isa(surface2, 'delaunayTriangulation')
48
  S = surface2;
49
  surface2 = [];
50
  surface2.faces    = S.ConnectivityList;
51
  surface2.vertices = S.Points;
52
  clear S
53
end
54
ok1 = isstruct(surface1) && isfield(surface1, 'vertices') && isfield(surface1, 'faces');
55
ok2 = isstruct(surface2) && isfield(surface2, 'vertices') && isfield(surface2, 'faces');
56
assert(ok1, 'Surface #1 must be a struct with "faces" and "vertices" fields' );
57
assert(ok2, 'Surface #2 must be a struct with "faces" and "vertices" fields' );
58
59
%% Flip dimentions if necessery
60
if size(surface1.faces,1)==3 && size(surface1.faces,2)~=3
61
  surface1.faces = surface1.faces';
62
end
63
if size(surface1.vertices,1)==3 && size(surface1.vertices,2)~=3
64
  surface1.vertices = surface1.vertices';
65
end
66
if size(surface2.faces,1)==3 && size(surface2.faces,2)~=3
67
  surface2.faces = surface2.faces';
68
end
69
if size(surface2.vertices,1)==3 && size(surface2.vertices,2)~=3
70
  surface2.vertices = surface2.vertices';
71
end
72
73
%% Parse extra parameters
74
getIntersection = (nargout>1);
75
debug = true;
76
PointRoundingTol = 1e6;
77
algorithm = 'moller';
78
k=1;
79
nVarargs = length(varargin);
80
while (k<=nVarargs)
81
  assert(ischar(varargin{k}), 'Incorrect input parameters')
82
  switch lower(varargin{k})
83
    case 'debug'
84
      debug = varargin{k+1}~=0;
85
      k = k+1;
86
    case 'algorithm'
87
      algorithm = lower(strtrim(varargin{k+1}));
88
      k = k+1;
89
    case 'pointroundingtol'
90
      PointRoundingTol = varargin{k+1};
91
      k = k+1;
92
  end
93
  k = k+1;
94
end
95
96
%% Initialize variables
97
epsilon = eps;
98
nFace1 = size(surface1.faces,1);
99
nFace2 = size(surface2.faces,1);
100
nVert1 = size(surface1.vertices,1);
101
nVert2 = size(surface2.vertices,1);
102
103
%% create strip down versions of MATLAB cross and dot function
104
cross_prod = @(a,b) [...
105
  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
106
  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
107
  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
108
dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
109
normalize = @(V) bsxfun(@rdivide,V, sqrt(sum(V.^2,2)));
110
111
%% Initialize output variables
112
% intersect is a nFace1 x nFace2 matrix. Possible values: -2 (do not know),
113
% -1 (coplanar with unknown overlap), 0 (no intersections), 1 (intersects).
114
% Negative values are internal only.
115
intMatrix  = zeros([nFace1,nFace2], 'int8')-2; % -2 indicates that there was no succesful test yet
116
intSurface.vertices = [];
117
intSurface.faces    = [];
118
intSurface.edges    = [];
119
120
% =======================================================================
121
%% === Stage 1 ==========================================================
122
% =======================================================================
123
% Each triangle is a subset of the plane it lies in, so for two triangles
124
% to intersect they must overlap along the line of intersection of their
125
% planes. Hence, a necessary condition for intersection is that each
126
% triangle must intersect the plane of the other.
127
% Mllers method begins by checking the mutual intersection of each
128
% triangle with the plane of the other. To do so, it determines for each
129
% triangle on which side of the other triangles supporting plane its
130
% vertices lie. Now, if all vertices of one triangle lie on the same side
131
% and no vertex is on the plane, the intersection is rejected.
132
133
%% compute plane equations for each triangle of the surface #1
134
% plane equation #1: N1.X-d1=0
135
V1 = surface1.vertices(surface1.faces(:,1),:);
136
V2 = surface1.vertices(surface1.faces(:,2),:);
137
V3 = surface1.vertices(surface1.faces(:,3),:);
138
N1 = cross_prod(V2-V1,V3-V1); % array size nFace1 x 3
139
N1 = normalize(N1);
140
d1 = dot_prod(N1,V1);         % array size nFace1 x 1
141
142
%% Distance from surface #2 vertices to planes of surface #1
143
% Calculate signed distance from all vertices of surface #2 to each plane
144
% of of surface #1
145
du = zeros(nFace1,nVert2);
146
for iVert2 = 1:nVert2
147
  p = surface2.vertices(iVert2,:);
148
  du(:,iVert2) = N1(:,1)*p(1) + N1(:,2)*p(2) + N1(:,3)*p(3) - d1;
149
end
150
if debug
151
  assert(all(size(du)==[nFace1,nVert2]), 'Incorrect array dimensions: dv')
152
end
153
du(abs(du)<epsilon)=0; % robustness check
154
% Distances from vertex 1, 2 & 3 of faces of surface #2 to planes of surface #1
155
du1 = du(:,surface2.faces(:,1));
156
du2 = du(:,surface2.faces(:,2));
157
du3 = du(:,surface2.faces(:,3));
158
if debug
159
  assert(all(size(du1)==size(intMatrix)), 'Incorrect array dimensions: du1')
160
end
161
clear du
162
intMatrix(du1.*du2>0 & du1.*du3>0) = 0;   % same sign on all of them & not equal 0
163
if(all(intMatrix==0)), return; end        % no intersections
164
intMatrix(du1==0 & du2==0 & du3==0) = -1; % coplanar with unknown overlap
165
166
%% compute plane of triangle (U0,U1,U2)
167
% plane equation 2: N2.X-d2=0
168
U1 = surface2.vertices(surface2.faces(:,1),:);
169
U2 = surface2.vertices(surface2.faces(:,2),:);
170
U3 = surface2.vertices(surface2.faces(:,3),:);
171
N2 = cross_prod(U2-U1,U3-U1); % array size nFace1 x 3
172
N2 = normalize(N2);
173
d2 = dot_prod(N2,U1);        % array size nFace1 x 1
174
175
%% Distance from surface #1 vertices to planes of surface #2
176
% Calculate signed distance from all vertices of surface #1 to each plane
177
% of of surface #2
178
dv = zeros(nFace2,nVert1);
179
for iVert1 = 1:nVert1
180
  p = surface1.vertices(iVert1,:);
181
  dv(:,iVert1) = N2(:,1)*p(1) + N2(:,2)*p(2) + N2(:,3)*p(3) - d2;
182
end
183
if debug
184
  assert(all(size(dv)==[nFace2,nVert1]), 'Incorrect array dimensions: dv')
185
end
186
dv(abs(dv)<epsilon)=0; % robustness check
187
% Distances from vertex 1, 2 & 3 of faces of surface #1 to planes of surface #2
188
dv1 = dv(:,surface1.faces(:,1))';
189
dv2 = dv(:,surface1.faces(:,2))';
190
dv3 = dv(:,surface1.faces(:,3))';
191
if debug
192
  assert(all(size(dv1)==size(intMatrix)), 'Incorrect array dimensions: dv1')
193
end
194
clear dv
195
intMatrix(dv1.*dv2>0 & dv1.*dv3>0) = 0;   % same sign on all of them & not equal 0
196
if(all(intMatrix==0)), return; end        % no intersections
197
intMatrix(dv1==0 & dv2==0 & dv3==0) = -1; % coplanar with unknown overlap
198
199
% =======================================================================
200
%% === Stage 2 ==========================================================
201
% =======================================================================
202
203
%% Process remaining (non-coplanar) triangle pairs
204
tMsk = (intMatrix==-2);
205
n = nnz(tMsk);
206
if n>0
207
  [face1, face2] = find(tMsk);
208
  switch lower(algorithm)
209
    case 'moller'
210
      if size(dv1(tMsk),1)==1
211
        dv = [dv1(tMsk)', dv2(tMsk)', dv3(tMsk)'];
212
        du = [du1(tMsk)', du2(tMsk)', du3(tMsk)'];
213
      else
214
        dv = [dv1(tMsk), dv2(tMsk), dv3(tMsk)];
215
        du = [du1(tMsk), du2(tMsk), du3(tMsk)];
216
      end
217
      
218
      [intMatrix(tMsk), intSurface] = TriangleIntersection3D_Moller(...
219
        V1(face1,:), V2(face1,:), V3(face1,:), N1(face1,:), d1(face1,:), dv, ...
220
        U1(face2,:), U2(face2,:), U3(face2,:), N2(face2,:), d2(face2,:), du, ...
221
        getIntersection, debug);
222
    case 'rapid'
223
      % Undocumented experimental feature. In some experiments I got
224
      % identical results as with Moller algorithm, but others gave
225
      % different results. Often faster tham Moller.
226
      intMatrix(tMsk) = TriangleIntersection3D_Rapid( ...
227
        V1(face1,:), V2(face1,:), V3(face1,:), ...
228
        U1(face2,:), U2(face2,:), U3(face2,:), N1(face1,:), N2(face2,:) );
229
    otherwise
230
      error('Unknown algorithm name');
231
  end
232
end % if
233
234
%% Process coplanar triangle pairs. Pass #1:
235
% compare the overlap of the bounding boxes
236
tMsk = (intMatrix==-1);
237
if nnz(tMsk)>0
238
  [face1, face2] = find(tMsk);
239
  overlap = true;
240
  for idim = 1:3
241
    v = [V1(face1,idim), V2(face1,idim), V3(face1,idim)];
242
    u = [U1(face2,idim), U2(face2,idim), U3(face2,idim)];
243
    t1 = min(v,[],2);
244
    t2 = max(v,[],2);
245
    s1 = min(u,[],2);
246
    s2 = max(u,[],2);
247
    overlap = overlap & (s1<=t2 & t1<=s2);
248
  end
249
  % if overlap intMatrix will remain "-1" otherwise it will change to "0"
250
  intMatrix(tMsk) = -1*overlap;
251
  clear v u t1 t2 s1 s2 overlap
252
end
253
254
%% Process coplanar triangle pairs. Pass #2:
255
% use edge-edge intersections
256
tMsk = (intMatrix==-1);
257
if nnz(tMsk)>0
258
  [face1, face2] = find(tMsk);
259
  
260
  % repack data prior to function call
261
  V(:,:,1)=V1(face1,:); V(:,:,2)=V2(face1,:); V(:,:,3)=V3(face1,:);
262
  U(:,:,1)=U1(face2,:); U(:,:,2)=U2(face2,:); U(:,:,3)=U3(face2,:);
263
  [intMatrix(tMsk), intSurface2] = TriangleIntersection2D(V, U, ...
264
    N1(face1,:), getIntersection, debug);
265
  
266
  %% Merge surfaces
267
  if getIntersection
268
    np = size(intSurface.vertices,1);
269
    intSurface.vertices = [intSurface.vertices; intSurface2.vertices];
270
    intSurface.faces    = [intSurface.faces;    intSurface2.faces+np];
271
    intSurface.edges    = [intSurface.edges;    intSurface2.edges+np];
272
    if debug
273
      np = size(intSurface.vertices,1);
274
      assert(max(intSurface.faces(:))<=np, 'Bad surface definition')
275
      assert(max(intSurface.edges(:))<=np, 'Bad surface definition')
276
    end
277
  end
278
end
279
280
%% Clean up the outputs
281
intMatrix = sparse(double(intMatrix));
282
if(getIntersection)
283
  % make point array unique
284
  P = round(intSurface.vertices*PointRoundingTol)/PointRoundingTol;
285
  [~,ia,ic] = unique(P,'rows'); % V = P(ia,:) and P = V(ic,:).
286
  intSurface.vertices = intSurface.vertices(ia,:);
287
  intSurface.faces = ic(intSurface.faces);
288
  intSurface.edges = ic(intSurface.edges);
289
end
290
end % function
291
292
%% ========================================================================
293
function [iMsk, intSurface] = TriangleIntersection3D_Moller(...
294
  V1, V2, V3, N1, d1, dv, ...
295
  U1, U2, U3, N2, d2, du, ...
296
  getIntersection, debug)
297
%TriangleIntersection3D tests if 2 triangles defined in 3D intersect.
298
% This is a secondary test following Tomas Moller algorithm
299
%
300
% INPUTS:
301
%   V1, V2, V3, - Nx3 array of surface 1 triangle vertex coordinates
302
%   U1, U2, U3, - Nx3 array of surface 2 triangle vertex coordinates
303
%   N1, d1      - Nx3 array of surface 1 triangle plane equations N1.X-d1=0
304
%   N2, d2      - Nx3 array of surface 2 triangle plane equations N2.X-d2=0
305
%   dv          - Nx3 array of distances of surface 1 triangle vertices to surface 2 planes
306
%   du          - Nx3 array of distances of surface 2 triangle vertices to surface 1 planes
307
%   getIntersection - do we need to output the intersecting surface?
308
%      Algorithm is much simpler if we do not.
309
%   debug       - In the debugging mode much more extra "sanity check" test
310
%      are performed.
311
%
312
% OUTPUT:
313
%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
314
%   intSurface - intersection surface
315
%
316
% ALGORITHM:
317
% The input triangles are guaranteed to intersect the line of intersection
318
% of the two planes. Furthermore, these intersections form intervals on
319
% this line, and the triangles overlap iff these intervals overlap as well.
320
% Hence, the last part of  the algorithm computes a parametric equation
321
% L(t) of the line of intersection of the two planes, finds the intervals
322
% (i.e. scalar intervals on L(t)) for which the line lies inside each
323
% triangle and performs a one-dimensional interval overlap test.
324
if debug
325
  ok = size(N1,2)==3 && size(N2,2)==3 && size(dv,2)==3 && size(du,2)==3 && ...
326
    size(V1,2)==3 && size(V2,2)==3 && size(V3,2)==3 && ...
327
    size(U1,2)==3 && size(U2,2)==3 && size(U3,2)==3;
328
  assert(ok, 'Incorrect array dimensions');
329
end
330
331
%% create strip down versions of MATLAB cross and dot function
332
cross_prod = @(a,b) [...
333
  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
334
  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
335
  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
336
dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
337
normalize = @(V) bsxfun(@rdivide,V, sqrt(sum(V.^2,2)));
338
339
%% Find intervals of surface 1 and 2 triangles
340
% compute the scalar intervals on L(t) for which the line lies inside each
341
% triangle
342
343
% Plane creates two open half-spaces. Find the odd vertex, which:
344
% 1) if no or two vertices are on the plane than pick the vertex which is
345
%    by itself in its half-space
346
% 2) if one vertex is on the plane and the other two occupy the same
347
%    half-space than pick the vertex on the plane
348
% 3) if one vertex is on the plane and the other two occupy different
349
%    half-spaces than pick one of the vertices off the plane
350
% Find vertex using a look-up table "lut" with key calculated based on
351
% sign of dv and du arrays
352
lut = [0;3;3;2;1;3;2;2;1;1;2;3;3;0;3;3;2;1;1;2;2;3;1;2;3;3;0];
353
n = numel(d1);
354
rows = (1:n)';
355
356
%% order surface 1 triangle vertices
357
a1 = lut(sign(dv)*[9; 3; 1] + 14); % calculate the key and call the look-up table
358
[b1, c1] = otherDim(a1);
359
if debug
360
  assert(all(a1>0), 'Something Wrong: triangles are coplanar')
361
end
362
a1 = sub2ind([n,3],rows,a1); % convert row and column IDs to array indecies
363
b1 = sub2ind([n,3],rows,b1);
364
c1 = sub2ind([n,3],rows,c1);
365
366
%% order surface 2 triangle vertices
367
a2 = lut(sign(du)*[9; 3; 1] + 14); % calculate the key and call the look-up table
368
[b2, c2] = otherDim(a2);
369
if debug
370
  assert(all(a2>0), 'Something Wrong: triangles are coplanar')
371
end
372
a2 = sub2ind([n,3],rows,a2);
373
b2 = sub2ind([n,3],rows,b2);
374
c2 = sub2ind([n,3],rows,c2);
375
376
%% compute direction of L the line of intersection of 2 planes
377
% containing 2 triangles. Line L parametric equation: t*D+O=0
378
D = cross_prod(N1,N2);    % D must be perpendicular to both N1 and N2
379
[~, maxDim] = max(abs(D),[],2); % compute and index to the largest component of D
380
if(getIntersection)
381
  D = normalize(D);
382
  O = zeros(n,3);
383
  d = [d1, d2, zeros(n,1)];
384
  for r =1:n
385
    N = [N1(r,:); N2(r,:); 0, 0, 0];
386
    N(3,maxDim(r)) = 1;
387
    dd = d(r,:)';
388
    O(r,:) = (N\dd)'; %Solve systems of linear equations N*D3 = d for D3
389
  end
390
  clear N d dd
391
end
392
393
%% projection of triangle(V1,V2,V3) and triangle(U1,U2,U3) onto intersection line
394
% Vp and Up are Nx3 arrays with columns indicating corners of triangles 1 and 2
395
if(getIntersection)
396
  Vp=[dot_prod(V1-O,D), dot_prod(V2-O,D), dot_prod(V3-O,D)];
397
  Up=[dot_prod(U1-O,D), dot_prod(U2-O,D), dot_prod(U3-O,D)];
398
else
399
  % Project on one of the axis (closest to the intersection line) instead.
400
  % Simplified projection is faster and sufficient if we do not need
401
  % intersection line
402
  idx = sub2ind([n,3],rows,maxDim);
403
  Vp = [V1(idx), V2(idx), V3(idx)];
404
  Up = [U1(idx), U2(idx), U3(idx)];
405
end
406
clear V1 V2 V3 U1 U2 U3
407
408
%% Calculate surface 1 and 2 triangle intervals
409
% t1 and t2 are intersection points of surface 1 with the intersection line
410
% t*D+O=0, and s1 & s2 are intersection points of surface 2 with the same
411
% line. Tomas Moller algorithm made this section much more complicated
412
% trying to avoid divisions. However, I could not detect any speed-up.
413
% Operations (ADD: 12; MUL:4 ; DIV:4 )
414
t1 = Vp(a1) - (Vp(b1)-Vp(a1)).*dv(a1)./(dv(b1)-dv(a1));
415
t2 = Vp(a1) - (Vp(c1)-Vp(a1)).*dv(a1)./(dv(c1)-dv(a1));
416
s1 = Up(a2) - (Up(b2)-Up(a2)).*du(a2)./(du(b2)-du(a2));
417
s2 = Up(a2) - (Up(c2)-Up(a2)).*du(a2)./(du(c2)-du(a2));
418
419
%% Order the intervals as to t1<t2 and s1<s2
420
msk = t2<t1; % order t1 and t2 so t1<t2
421
t = t1(msk); t1(msk)=t2(msk); t2(msk)=t; % swap
422
msk = s2<s1; % order s1 and s2 so s1<s2
423
t = s1(msk); s1(msk)=s2(msk); s2(msk)=t; % swap
424
425
%% Perform THE final test we were preparying for.
426
% It test for the overlap of 2 1D intervals s1->s2 and t1->t2
427
iMsk = (s1<t2 & t1<s2);
428
429
%% calculate intersection segments
430
n = nnz(iMsk);
431
if(getIntersection && n>0)
432
  % p1 = D*max(t1,s1) + O;    p2 = D*min(t2,s2) + O
433
  p1 = bsxfun(@times,D(iMsk,:),max(t1(iMsk),s1(iMsk))) + O(iMsk,:);
434
  p2 = bsxfun(@times,D(iMsk,:),min(t2(iMsk),s2(iMsk))) + O(iMsk,:);
435
  intSurface.vertices = [p1; p2];
436
  intSurface.faces    = [1:n; n+1:2*n; n+1:2*n]';
437
  intSurface.edges    = intSurface.faces(:,1:2);
438
else
439
  intSurface.vertices = [];
440
  intSurface.faces    = [];
441
  intSurface.edges    = [];
442
end % if
443
end % function
444
445
%% ========================================================================
446
function [overlap, intSurface] = TriangleIntersection2D(V, U, N, ...
447
  getIntersection, debug)
448
% Triangles V(V0,V1,V2) and U(U0,U1,U2) are are coplanar. Do they overlap?
449
% INPUTS:
450
% N - array(n,3) of surface normals where V(i,:,:) and U(i,:,:) are on the same plane
451
% V - array(n,3,3) (nFace x 3 dimensions x 3 vertices) of surface #1 vertices
452
% U - array(n,3,3) (nFace x 3 dimensions x 3 vertices) of surface #2 vertices
453
%
454
% OUTPUT:
455
%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
456
%   intSurface - intersection surface
457
458
459
%  * parameters: vertices of triangle 1: V0,V1,V2
460
%  *             vertices of triangle 2: U0,U1,U2
461
%  * result    : returns 1 if the triangles intersect, otherwise 0
462
463
%% Constants needed for creating a mesh based on 3 to 6 points in a circle
464
tri_mesh{6}  = [1 2 6; 2 4 6; 2 3 4; 4 5 6];
465
tri_mesh{5}  = [1 2 3; 1 3 4; 4 5 1];
466
tri_mesh{4}  = [1 2 3; 1 3 4];
467
tri_mesh{3}  = 1:3;
468
vertices = [];
469
faces    = [];
470
pairs    = [];  % each row corresponds to pair of faces. match row number with face number
471
nVert    = 0;
472
473
%% use edge-edge intersections
474
overlap = false(size(N,1),1);
475
i1Idx = [1 1 1 2 2 2 3 3 3];
476
i2Idx = [3 3 3 1 1 1 2 2 2];
477
j1Idx = [1 2 3 1 2 3 1 2 3];
478
j2Idx = [3 1 2 3 1 2 3 1 2];
479
for row = 1:size(N,1)
480
  % When it is necesary to project 3D plane on 2D, dIdx will be the optimal
481
  % dimensions to use.
482
  [~, a] = max(abs(N(row,:))); 
483
  [b, c] = otherDim(a); 
484
  dIdx = [b, c]; 
485
  order = [];
486
487
  %% test all edges of triangle 1 against the edges of triangle 2
488
  % triangles overlap if edges cross
489
  [edgeMat, P] = EdgesIntersect3D(...
490
    squeeze(V(row,:,i1Idx))',squeeze(V(row,:,i2Idx))', ...
491
    squeeze(U(row,:,j1Idx))',squeeze(U(row,:,j2Idx))');
492
  overlap(row) = any(edgeMat);
493
  if ~getIntersection && overlap(row), continue; end
494
  
495
  if ~overlap(row)
496
    %% project onto an axis-aligned plane, that maximizes the area
497
    % of the triangles, compute indices: dIdx which correspond to 2 smallest N1
498
    % components.
499
    V2d = [V(row,dIdx,1); V(row,dIdx,2); V(row,dIdx,3)]; % each row is a 2D vertex
500
    U2d = [U(row,dIdx,1); U(row,dIdx,2); U(row,dIdx,3)];
501
    
502
    %% test if tri1 is totally contained in tri2 or vice varsa
503
    if PointInTriangle2D(V2d(1,:), U2d) % tri1 is totally contained in tri2
504
      overlap(row) = true;
505
      order = 1:3;
506
    elseif PointInTriangle2D(U2d(1,:), V2d) % tri2 is totally contained in tri1
507
      overlap(row) = true;
508
      order = 4:6;
509
    end
510
    if overlap(row) && ~getIntersection, continue; end
511
    clear V2d U2d
512
  end
513
  
514
  %% Build the intersection surface
515
  if getIntersection && overlap(row)
516
    %Assemble all the points which might be needed for desining
517
    %intersection polygon: Intersection points and points from triangle 1
518
    %and 2
519
    points   = [P(edgeMat,:); squeeze(V(row,:,:))'; squeeze(U(row,:,:))'];
520
    if isempty(order) % when one tri is totally contained in the other tri then order is set
521
      order = IntersectionPolygon(edgeMat>0, points, dIdx, debug);
522
      if isempty(order), continue; end
523
    end
524
    nPoint   = length(order);    % how many points will be added?
525
    nFace    = nPoint-2;         % how many faces will be added?
526
    vertices = [vertices; points(order,:)]; %#ok<*AGROW>
527
    faces    = [faces; nVert+tri_mesh{nPoint} ];
528
    pairs    = [pairs; row+zeros(nFace,1)];  % each row corresponds to pair of faces. match row number with face number
529
    nVert    = nVert + nPoint;
530
    if debug
531
      assert(max(faces(:))<=size(vertices,1), 'Bad surface definition')
532
    end
533
  end
534
end % for
535
536
%% Prepare outputs
537
intSurface.vertices = vertices;
538
intSurface.faces    = faces;
539
if isempty(faces)
540
  intSurface.edges = [];
541
else
542
  intSurface.edges = [faces(:,1:2); faces(:,2:3); faces(:,[1,3])];
543
end
544
end % function
545
546
%% ========================================================================
547
function polygon = IntersectionPolygon(edgeMat, points, dIdx, debug)
548
% edgeMat is an edge intersection matrix with 3 rows for edges between
549
% the points 1-3, 1-2, & 2-3 of the triangle 1 and 3 columns for the same
550
% edges of the triangle 2. If 2 edges intersect a point of intersection
551
% is calculated and stored in array "points" followed by points of the
552
% triangles 1 & 2.  This function calculates the polygon of the intersection
553
% between 2 triangles.
554
555
persistent orderLUT verified
556
if isempty(orderLUT) || isempty(orderLUT{3})
557
  % This pre-calculated look-up table is used to quickly look up the order of
558
  % the vertices in array "points" which make up polygon of the intersection
559
  % between 2 triangles. A unique key is calculated for each edgeMat using
560
  % dot product between edgeMat(:) and [256 128 64 32 16 8 4 2 1], which is
561
  % used to look up point order around the polygon. Negative numbers in the
562
  % LUT indicate values which were not observed yet so they were not
563
  % independently verified.
564
  % reshape(sprintf('%09s',dec2base(key, 2)),3,3) will convert from the key
565
  % to matrix.
566
  OrderLUT = zeros(432,1);  
567
  OrderLUT(003) = 127;
568
  OrderLUT(005) = 128;
569
  OrderLUT(006) = 126;
570
  OrderLUT(009) = 124;
571
  OrderLUT(010) = 1427;
572
  OrderLUT(012) = 1428;
573
  OrderLUT(017) = 1427;
574
  OrderLUT(018) = 124;
575
  OrderLUT(020) = 1426;
576
  OrderLUT(024) = 127;
577
  OrderLUT(027) = 1243;
578
  OrderLUT(029) = 12438;
579
  OrderLUT(030) = 12034;
580
  OrderLUT(033) = 1428;
581
  OrderLUT(034) = 1426;
582
  OrderLUT(036) = 124;
583
  OrderLUT(040) = 128;
584
  OrderLUT(043) = 21834;
585
  OrderLUT(045) = 1243;
586
  OrderLUT(046) = 21349;
587
  OrderLUT(048) = 126;
588
  OrderLUT(051) = 12340;
589
  OrderLUT(053) = 12943;
590
  OrderLUT(054) = 1243;
591
  OrderLUT(065) = 125;
592
  OrderLUT(066) = 1527;
593
  OrderLUT(068) = 1825;
594
  OrderLUT(072) = 123;
595
  OrderLUT(080) = 1327;
596
  OrderLUT(083) = 15234;
597
  OrderLUT(085) = -15234;
598
  OrderLUT(086) = -15243;
599
  OrderLUT(090) = 13247;
600
  OrderLUT(092) = -13247;
601
  OrderLUT(096) = 1328;
602
  OrderLUT(099) = 152834;
603
  OrderLUT(101) = 15234;
604
  OrderLUT(102) = 152349;
605
  OrderLUT(106) = 132847;
606
  OrderLUT(108) = 13247;
607
  OrderLUT(114) = 102347;
608
  OrderLUT(116) = -13247;
609
  OrderLUT(129) = 1527;
610
  OrderLUT(130) = 125;
611
  OrderLUT(132) = 1526;
612
  OrderLUT(136) = 1327;
613
  OrderLUT(139) = 15243;
614
  OrderLUT(141) = 152438;
615
  OrderLUT(142) = 152034;
616
  OrderLUT(144) = 123;
617
  OrderLUT(153) = 12347;
618
  OrderLUT(156) = 123047;
619
  OrderLUT(160) = 1326;
620
  OrderLUT(163) = -152043;
621
  OrderLUT(165) = 13247;
622
  OrderLUT(166) = 15234;
623
  OrderLUT(169) = -182347;
624
  OrderLUT(172) = 193247;
625
  OrderLUT(177) = -132047;
626
  OrderLUT(180) = 13247;
627
  OrderLUT(192) = 127;
628
  OrderLUT(195) = 1243;
629
  OrderLUT(197) = 12438;
630
  OrderLUT(198) = 12034;
631
  OrderLUT(202) = 12364;
632
  OrderLUT(204) = 123648;
633
  OrderLUT(209) = 21364;
634
  OrderLUT(212) = -21364;
635
  OrderLUT(216) = 1243;
636
  OrderLUT(225) = -124638;
637
  OrderLUT(226) = 120364;
638
  OrderLUT(232) = 12438;
639
  OrderLUT(238) = 124356;
640
  OrderLUT(240) = 12034;
641
  OrderLUT(245) = -214356;
642
  OrderLUT(257) = 1528;
643
  OrderLUT(258) = 1526;
644
  OrderLUT(260) = 125;
645
  OrderLUT(264) = 1328;
646
  OrderLUT(267) = -152438;
647
  OrderLUT(269) = 15243;
648
  OrderLUT(270) = -152943;
649
  OrderLUT(272) = 1326;
650
  OrderLUT(275) = 152340;
651
  OrderLUT(277) = 152943;
652
  OrderLUT(278) = 15243;
653
  OrderLUT(281) = 182347;
654
  OrderLUT(282) = -103247;
655
  OrderLUT(288) = 123;
656
  OrderLUT(297) = 12347;
657
  OrderLUT(298) = -123947;
658
  OrderLUT(305) = 123947;
659
  OrderLUT(306) = 12347;
660
  OrderLUT(320) = 128;
661
  OrderLUT(323) = 21834;
662
  OrderLUT(325) = 1243;
663
  OrderLUT(326) = 21349;
664
  OrderLUT(330) = -123648;
665
  OrderLUT(332) = 12364;
666
  OrderLUT(337) = 183642;
667
  OrderLUT(340) = -129364;
668
  OrderLUT(344) = 21834;
669
  OrderLUT(350) = -124365;
670
  OrderLUT(353) = 12463;
671
  OrderLUT(354) = 136492;
672
  OrderLUT(360) = 1243;
673
  OrderLUT(368) = 12943;
674
  OrderLUT(371) = 126543;
675
  OrderLUT(384) = 126;
676
  OrderLUT(387) = 12340;
677
  OrderLUT(389) = 12943;
678
  OrderLUT(390) = 1243;
679
  OrderLUT(394) = -103642;
680
  OrderLUT(396) = 129364;
681
  OrderLUT(401) = 123640;
682
  OrderLUT(404) = 12364;
683
  OrderLUT(408) = 12340;
684
  OrderLUT(413) = 215643;
685
  OrderLUT(417) = -136492;
686
  OrderLUT(418) = 12463;
687
  OrderLUT(424) = 13492;
688
  OrderLUT(427) = -213456;
689
  OrderLUT(432) = 1342;
690
  
691
  % Convert to more convinient format
692
  orderLUT = cell(size(OrderLUT));
693
  for i = 1:size(OrderLUT,1)
694
    polygon = abs(OrderLUT(i));
695
    if polygon>0
696
      polygon = num2str(polygon)-48; % Convert from a single number to array of digits
697
      polygon(polygon==0) = 10;      % 0 stands for 10
698
      orderLUT{i} = polygon;
699
    end
700
  end
701
  % Negative numbers in the LUT indicate values which were not observed yet
702
  % so they were not independently verified.
703
  verified = OrderLUT>0;
704
  clear OrderLUT
705
end
706
707
%% Calculate unique key for each edgeMat configuration
708
key = dot(1*edgeMat(:)', [256 128 64 32 16 8 4 2 1]);
709
assert(key<=432, 'Error: in IntersectionPolygon: key is out of bound');
710
711
%% Look up the point order around the polygon
712
polygon = orderLUT{key};
713
if (isempty(polygon))
714
  return
715
end
716
717
%% in a rare case of 2 intersections there is ambiguity if one or two
718
% vertices of the triangle lay inside the other triangle. OrderLUT stores
719
% only the single vertex cases.
720
nx = nnz(edgeMat(:));
721
if nx==2
722
  pList = polygon;       % list of vertices to check
723
  pList(pList<=nx) = []; % keep only the triangle points of the polygon
724
  flip = false;    % was there a flip from single vertex to vertices case?
725
  for ip = 1:length(pList)
726
    p = pList(ip);                 % point to check
727
    t = floor((p-nx-1)/3);         % does it belong to triangle 0 or 1 (actually 1 or 2)
728
    tri = (1:3) + nx + 3*abs(1-t); % Points belonging to the other triangle
729
    if ~PointInTriangle2D(points(p,dIdx), points(tri,dIdx))
730
      d = nx+t*3;    % offset
731
      % "p-d" is vertex number of point just tested: 1, 2, or 3. "b, c" are
732
      % the other 2 vertices
733
      [b, c] = otherDim(p-d);
734
      polygon = [polygon(polygon~=p), b+d, c+d]; % remove i2 and add i0 and i1
735
      flip = true;
736
    end
737
  end
738
  if flip
739
    % if ther were any flips than use existing codes to figure out the
740
    % order of the points around the polygon
741
    DT = delaunayTriangulation(points(polygon,dIdx));
742
    idx = freeBoundary(DT)';
743
    idx(2,:) = [];
744
    polygon = polygon(idx);
745
  end
746
end
747
748
%% Check to duplicate points
749
tol = 1e6;
750
P = round(points(polygon,:)*tol)/tol;
751
[~,ia] = unique(P,'rows'); % V = P(ia,:) and P = V(ic,:).
752
polygon = polygon(sort(ia));
753
754
%% Test the results using more expensive function
755
doPlot = (~verified(key));
756
if debug && length(polygon)>3
757
  DT = delaunayTriangulation(points(polygon,dIdx));
758
  idx = freeBoundary(DT)';
759
  idx(2,:) = [];
760
  k = max(abs(diff(idx)));
761
  %doPlot = (k>1 && k<(length(idx)-1)) || (~verified(key));
762
  assert(k==1 || k==(length(idx)-1), 'Two triangle intersection polygon is not convex')
763
end
764
if debug && doPlot % plot the interesting cases
765
  PlotTwoTriangles(points, polygon, 'm')
766
  title(sprintf('key = %i', key));
767
end 
768
769
end % function
770
771
%% ========================================================================
772
function PlotTwoTriangles(points, polygon, color)
773
% Plotting function used for debugging
774
nx = size(points,1)-6;
775
d = (max(points,[],1)-min(points,[],1))/200;
776
figure(2)
777
clf
778
hold on
779
line( points(nx+(1:2),1), points(nx+(1:2),2), points(nx+(1:2),3), 'Color', 'g');
780
line( points(nx+(2:3),1), points(nx+(2:3),2), points(nx+(2:3),3), 'Color', 'g');
781
line( points(nx+[1,3],1), points(nx+[1,3],2), points(nx+[1,3],3), 'Color', 'g');
782
line( points(nx+(4:5),1), points(nx+(4:5),2), points(nx+(4:5),3), 'Color', 'b');
783
line( points(nx+(5:6),1), points(nx+(5:6),2), points(nx+(5:6),3), 'Color', 'b');
784
line( points(nx+[4,6],1), points(nx+[4,6],2), points(nx+[4,6],3), 'Color', 'b');
785
plot3( points(:,1), points(:,2), points(:,3), 'm.');
786
if (length(polygon)>2)
787
  idx = polygon([1:end, 1]);
788
  plot3( points(idx,1), points(idx,2),points(idx,3), 'Color', color, 'LineWidth', 1);
789
end
790
for i = 1:nx+6
791
  text(points(i,1)+d(1), points(i,2)+d(2), points(i,3), num2str(i))
792
end
793
794
end % function
795
796
%% ========================================================================
797
function [intersect, X] = EdgesIntersect3D(V1,V2, U1,U2)
798
%EdgesIntersectPoint3D calculates point of intersection of 2 coplanar
799
% segments in 3D
800
%
801
% INPUTS:
802
%   V1,V2 - 1 x 3 coordinates of endpoints of edge 1
803
%   U1,U2 - 1 x 3 coordinates of endpoints of edge 2
804
% OUTPUT:
805
%   X - 1 x 3 coordinates of the intersection point
806
A = V2-V1;
807
B = U1-U2;
808
C = U1-V1;
809
%% Solve system of equations [A,B,1] * [d;e;0] = C for d and e
810
det3 = @(a,b) ... % determinant of a matrix with columns: [a, b, 1]
811
  a(:,1).*b(:,2)-a(:,3).*b(:,2) + ...
812
  a(:,2).*b(:,3)-a(:,2).*b(:,1) + ...
813
  a(:,3).*b(:,1)-a(:,1).*b(:,3);
814
f=det3(A,B); % https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems
815
t=det3(C,B)./f; % use Cramer's rule
816
s=det3(A,C)./f;
817
intersect = (t>=0 & t<=1 & s>=0 & s<=1);
818
X = V1 + bsxfun(@times,A,t);
819
end % function
820
821
%% ========================================================================
822
function inside = PointInTriangle2D(V1, U)
823
% check if V1 is inside triangle U (U1,U2,U3)
824
% Algorithm is checking on which side of the half-plane created by the
825
% edges the point is. It uses sign of determinant to calculate orientation
826
% of point triplets.
827
% INPUTS:
828
%   V1 - 1 x 2 coordinates of a point
829
%   U  - 3 x 2 coordinates of endpoints of 3 edges of a triangle
830
% OUTPUT:
831
%   inside - a boolean or boolean array
832
det2 = @(A,B,C) (A(:,1)-C(:,1))*(B(:,2)-C(:,2)) - (B(:,1)-C(:,1))*(A(:,2)-C(:,2));
833
b1 = (det2(U(1,:), U(2,:), V1) > 0);
834
b2 = (det2(U(2,:), U(3,:), V1) > 0);
835
b3 = (det2(U(3,:), U(1,:), V1) > 0);
836
inside = ((b1 == b2) & (b2 == b3)); % inside if same orientation for all 3 edges
837
end % function
838
839
%% ========================================================================
840
function [b, c] = otherDim(a)
841
% return set [1 2 3] without k
842
b = mod(a+1,3)+1;  % b and c are vertices which are on the same side of the plane
843
c = 6-a-b;         % a+b+c = 6
844
end
845
846
847
%% ========================================================================
848
function overlap = TriangleIntersection3D_Rapid( v1, v2, v3, u1, u2, u3, n1, n2 )
849
%TriangleIntersection3D tests if 2 triangles defined in 3D intersect.
850
%
851
% INPUTS:
852
%   v1, v2, v3, - Nx3 array of surface 1 triangle vertex coordinates
853
%   u1, u2, u3, - Nx3 array of surface 2 triangle vertex coordinates
854
%   n1, n2      - Nx3 array of surface 1 & 2 triangle plane normals. Those
855
%      are optional and if provided than the first 2 steps of the algorithm
856
%      (which are equivalent to first 2 steps of Moller algorithm) will be 
857
%      skipped.
858
%
859
% OUTPUT:
860
%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
861
%
862
% ALGORITHM:
863
%   translated from the UNC-CH V-Collide RAPID code
864
%    https://wwwx.cs.unc.edu/~geom/papers/COLLISION/vcol.pdf
865
866
global V1 V2 V3 U1 U2 U3
867
868
cross_prod = @(a,b) [...
869
  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
870
  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
871
  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
872
873
%% shift t1 and t2 by p1#
874
V1 = zeros(size(v1));
875
V2 = v2-v1;
876
V3 = v3-v1;
877
U1 = u1-v1;
878
U2 = u2-v1;
879
U3 = u3-v1;
880
clear v1 v2 v3 u1 u2 u3
881
882
if(nargin<7)
883
  %% now begin the series of tests
884
  n1 = cross_prod( V2-V1, V3-V1 ); % face normals
885
  n2 = cross_prod( U2-U1, U3-U1 ); % face normals
886
end
887
  
888
%% test the face normals
889
overlap = project6(n1) & project6(n2);
890
V1 = V1(overlap,:);
891
V2 = V2(overlap,:);
892
V3 = V3(overlap,:);
893
U1 = U1(overlap,:);
894
U2 = U2(overlap,:);
895
U3 = U3(overlap,:);
896
n1 = n1(overlap,:);
897
n2 = n2(overlap,:);
898
899
%% compute triangle edges
900
e1 = V2-V1;
901
e2 = V3-V2;
902
e3 = V1-V3;
903
f1 = U2-U1;
904
f2 = U3-U2;
905
f3 = U1-U3;
906
907
%% run more tests
908
overlap2 = project6(cross_prod(e1, f1));
909
overlap2 = project6(cross_prod(e1, f2)) & overlap2;
910
overlap2 = project6(cross_prod(e1, f3)) & overlap2;
911
overlap2 = project6(cross_prod(e2, f1)) & overlap2;
912
overlap2 = project6(cross_prod(e2, f2)) & overlap2;
913
overlap2 = project6(cross_prod(e2, f3)) & overlap2;
914
overlap2 = project6(cross_prod(e3, f1)) & overlap2;
915
overlap2 = project6(cross_prod(e3, f2)) & overlap2;
916
overlap2 = project6(cross_prod(e3, f3)) & overlap2;
917
overlap2 = project6(cross_prod(e1, n1)) & overlap2;
918
overlap2 = project6(cross_prod(e2, n1)) & overlap2;
919
overlap2 = project6(cross_prod(e3, n1)) & overlap2;
920
overlap2 = project6(cross_prod(f1, n2)) & overlap2;
921
overlap2 = project6(cross_prod(f2, n2)) & overlap2;
922
overlap2 = project6(cross_prod(f3, n2)) & overlap2;
923
overlap(overlap) = overlap2;
924
end
925
926
%% ========================================================================
927
function pass = project6( p )
928
% project all 6 vertices of both triangles onto vector p and check if two
929
% projections overlap
930
global V1 V2 V3 U1 U2 U3
931
dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
932
%% Project vertices of triangle 1 and find the bounds min1 and max1
933
P = [dot_prod(p, V1), dot_prod(p, V2), dot_prod(p, V3)];
934
max1 = max(P,[],2);
935
min1 = min(P,[],2);
936
%% Project vertices of triangle 2 and find the bounds min1 and max1
937
P = [dot_prod(p, U1), dot_prod(p, U2), dot_prod(p, U3)];
938
max2 = max(P,[],2);
939
min2 = min(P,[],2);
940
%% Compare the bounds to see if they overlap
941
pass = (( min1 < max2 ) & ( min2 < max1 )) | ~dot_prod(p, p);
942
end