Diff of /SurfaceIntersection.m [000000] .. [f949a5]

Switch to side-by-side view

--- a
+++ b/SurfaceIntersection.m
@@ -0,0 +1,942 @@
+function [intMatrix, intSurface] = SurfaceIntersection(surface1, surface2, varargin)
+%SURFACEINTERSECTION intersection of 2 surfaces
+% [intMatrix, intSurface] = SurfaceIntersection(surface1, surface2)
+% calculates the intersection of surfaces 1 and 2. Code can either return
+% just the matrix indicating which face of surface1 intersected with face
+% of surface2, which is calculated using Tomas Moller algorithm, or can
+% also return the actual line of intersection. In case when parts of the
+% surface 1 and 2 lay on the same plane the intersection is a 2D area
+% instead of 1D edge. In such a case the intersection area will be
+% triangulated and intSurface.edges will hold the edges of the
+% triangulation surface and intSurface.faces will hold the faces.
+%
+% INPUT:
+%  * surface1 & surface2 - two surfaces defined as structs or classes.
+%    Several inputs are possible:
+%    - struct with "faces" and "vertices" fields
+%    - 'triangulation' class (only the boundary surface will be used)
+%    - 'delaunayTriangulation' class
+%
+% OUTPUT:
+% * intMatrix - sparse Matrix with n1 x n2 dimension where n1 and n2 are
+%               number of faces in surfaces
+% * intSurface - a structure with following fields:
+%     intSurface.vertices - N x 3 array of unique points
+%     intSurface.edges    - N x 2 array of edge vertex ID's
+%     intSurface.faces    - N x 3 array of face vertex ID's
+%
+% ALGORITHM:
+% Based on Triangle/triangle intersection test routine by Tomas Mller, 1997.
+%  See article "A Fast Triangle-Triangle Intersection Test",
+%  Journal of Graphics Tools, 2(2), 1997
+%  http://web.stanford.edu/class/cs277/resources/papers/Moller1997b.pdf
+%  http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/opttritri.txt
+
+%% Get FACES and VERTICES inputs
+if isa(surface1, 'triangulation')
+  [surface1.faces, surface1.vertices] = freeBoundary(surface1);
+elseif isa(surface1, 'delaunayTriangulation')
+  S = surface1;
+  surface1 = [];
+  surface1.faces    = S.ConnectivityList;
+  surface1.vertices = S.Points;
+  clear S
+end
+if isa(surface2, 'triangulation')
+  [surface2.faces, surface1.vertices] = freeBoundary(surface2);
+elseif isa(surface2, 'delaunayTriangulation')
+  S = surface2;
+  surface2 = [];
+  surface2.faces    = S.ConnectivityList;
+  surface2.vertices = S.Points;
+  clear S
+end
+ok1 = isstruct(surface1) && isfield(surface1, 'vertices') && isfield(surface1, 'faces');
+ok2 = isstruct(surface2) && isfield(surface2, 'vertices') && isfield(surface2, 'faces');
+assert(ok1, 'Surface #1 must be a struct with "faces" and "vertices" fields' );
+assert(ok2, 'Surface #2 must be a struct with "faces" and "vertices" fields' );
+
+%% Flip dimentions if necessery
+if size(surface1.faces,1)==3 && size(surface1.faces,2)~=3
+  surface1.faces = surface1.faces';
+end
+if size(surface1.vertices,1)==3 && size(surface1.vertices,2)~=3
+  surface1.vertices = surface1.vertices';
+end
+if size(surface2.faces,1)==3 && size(surface2.faces,2)~=3
+  surface2.faces = surface2.faces';
+end
+if size(surface2.vertices,1)==3 && size(surface2.vertices,2)~=3
+  surface2.vertices = surface2.vertices';
+end
+
+%% Parse extra parameters
+getIntersection = (nargout>1);
+debug = true;
+PointRoundingTol = 1e6;
+algorithm = 'moller';
+k=1;
+nVarargs = length(varargin);
+while (k<=nVarargs)
+  assert(ischar(varargin{k}), 'Incorrect input parameters')
+  switch lower(varargin{k})
+    case 'debug'
+      debug = varargin{k+1}~=0;
+      k = k+1;
+    case 'algorithm'
+      algorithm = lower(strtrim(varargin{k+1}));
+      k = k+1;
+    case 'pointroundingtol'
+      PointRoundingTol = varargin{k+1};
+      k = k+1;
+  end
+  k = k+1;
+end
+
+%% Initialize variables
+epsilon = eps;
+nFace1 = size(surface1.faces,1);
+nFace2 = size(surface2.faces,1);
+nVert1 = size(surface1.vertices,1);
+nVert2 = size(surface2.vertices,1);
+
+%% create strip down versions of MATLAB cross and dot function
+cross_prod = @(a,b) [...
+  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
+  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
+  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
+dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
+normalize = @(V) bsxfun(@rdivide,V, sqrt(sum(V.^2,2)));
+
+%% Initialize output variables
+% intersect is a nFace1 x nFace2 matrix. Possible values: -2 (do not know),
+% -1 (coplanar with unknown overlap), 0 (no intersections), 1 (intersects).
+% Negative values are internal only.
+intMatrix  = zeros([nFace1,nFace2], 'int8')-2; % -2 indicates that there was no succesful test yet
+intSurface.vertices = [];
+intSurface.faces    = [];
+intSurface.edges    = [];
+
+% =======================================================================
+%% === Stage 1 ==========================================================
+% =======================================================================
+% Each triangle is a subset of the plane it lies in, so for two triangles
+% to intersect they must overlap along the line of intersection of their
+% planes. Hence, a necessary condition for intersection is that each
+% triangle must intersect the plane of the other.
+% Mllers method begins by checking the mutual intersection of each
+% triangle with the plane of the other. To do so, it determines for each
+% triangle on which side of the other triangles supporting plane its
+% vertices lie. Now, if all vertices of one triangle lie on the same side
+% and no vertex is on the plane, the intersection is rejected.
+
+%% compute plane equations for each triangle of the surface #1
+% plane equation #1: N1.X-d1=0
+V1 = surface1.vertices(surface1.faces(:,1),:);
+V2 = surface1.vertices(surface1.faces(:,2),:);
+V3 = surface1.vertices(surface1.faces(:,3),:);
+N1 = cross_prod(V2-V1,V3-V1); % array size nFace1 x 3
+N1 = normalize(N1);
+d1 = dot_prod(N1,V1);         % array size nFace1 x 1
+
+%% Distance from surface #2 vertices to planes of surface #1
+% Calculate signed distance from all vertices of surface #2 to each plane
+% of of surface #1
+du = zeros(nFace1,nVert2);
+for iVert2 = 1:nVert2
+  p = surface2.vertices(iVert2,:);
+  du(:,iVert2) = N1(:,1)*p(1) + N1(:,2)*p(2) + N1(:,3)*p(3) - d1;
+end
+if debug
+  assert(all(size(du)==[nFace1,nVert2]), 'Incorrect array dimensions: dv')
+end
+du(abs(du)<epsilon)=0; % robustness check
+% Distances from vertex 1, 2 & 3 of faces of surface #2 to planes of surface #1
+du1 = du(:,surface2.faces(:,1));
+du2 = du(:,surface2.faces(:,2));
+du3 = du(:,surface2.faces(:,3));
+if debug
+  assert(all(size(du1)==size(intMatrix)), 'Incorrect array dimensions: du1')
+end
+clear du
+intMatrix(du1.*du2>0 & du1.*du3>0) = 0;   % same sign on all of them & not equal 0
+if(all(intMatrix==0)), return; end        % no intersections
+intMatrix(du1==0 & du2==0 & du3==0) = -1; % coplanar with unknown overlap
+
+%% compute plane of triangle (U0,U1,U2)
+% plane equation 2: N2.X-d2=0
+U1 = surface2.vertices(surface2.faces(:,1),:);
+U2 = surface2.vertices(surface2.faces(:,2),:);
+U3 = surface2.vertices(surface2.faces(:,3),:);
+N2 = cross_prod(U2-U1,U3-U1); % array size nFace1 x 3
+N2 = normalize(N2);
+d2 = dot_prod(N2,U1);        % array size nFace1 x 1
+
+%% Distance from surface #1 vertices to planes of surface #2
+% Calculate signed distance from all vertices of surface #1 to each plane
+% of of surface #2
+dv = zeros(nFace2,nVert1);
+for iVert1 = 1:nVert1
+  p = surface1.vertices(iVert1,:);
+  dv(:,iVert1) = N2(:,1)*p(1) + N2(:,2)*p(2) + N2(:,3)*p(3) - d2;
+end
+if debug
+  assert(all(size(dv)==[nFace2,nVert1]), 'Incorrect array dimensions: dv')
+end
+dv(abs(dv)<epsilon)=0; % robustness check
+% Distances from vertex 1, 2 & 3 of faces of surface #1 to planes of surface #2
+dv1 = dv(:,surface1.faces(:,1))';
+dv2 = dv(:,surface1.faces(:,2))';
+dv3 = dv(:,surface1.faces(:,3))';
+if debug
+  assert(all(size(dv1)==size(intMatrix)), 'Incorrect array dimensions: dv1')
+end
+clear dv
+intMatrix(dv1.*dv2>0 & dv1.*dv3>0) = 0;   % same sign on all of them & not equal 0
+if(all(intMatrix==0)), return; end        % no intersections
+intMatrix(dv1==0 & dv2==0 & dv3==0) = -1; % coplanar with unknown overlap
+
+% =======================================================================
+%% === Stage 2 ==========================================================
+% =======================================================================
+
+%% Process remaining (non-coplanar) triangle pairs
+tMsk = (intMatrix==-2);
+n = nnz(tMsk);
+if n>0
+  [face1, face2] = find(tMsk);
+  switch lower(algorithm)
+    case 'moller'
+      if size(dv1(tMsk),1)==1
+        dv = [dv1(tMsk)', dv2(tMsk)', dv3(tMsk)'];
+        du = [du1(tMsk)', du2(tMsk)', du3(tMsk)'];
+      else
+        dv = [dv1(tMsk), dv2(tMsk), dv3(tMsk)];
+        du = [du1(tMsk), du2(tMsk), du3(tMsk)];
+      end
+      
+      [intMatrix(tMsk), intSurface] = TriangleIntersection3D_Moller(...
+        V1(face1,:), V2(face1,:), V3(face1,:), N1(face1,:), d1(face1,:), dv, ...
+        U1(face2,:), U2(face2,:), U3(face2,:), N2(face2,:), d2(face2,:), du, ...
+        getIntersection, debug);
+    case 'rapid'
+      % Undocumented experimental feature. In some experiments I got
+      % identical results as with Moller algorithm, but others gave
+      % different results. Often faster tham Moller.
+      intMatrix(tMsk) = TriangleIntersection3D_Rapid( ...
+        V1(face1,:), V2(face1,:), V3(face1,:), ...
+        U1(face2,:), U2(face2,:), U3(face2,:), N1(face1,:), N2(face2,:) );
+    otherwise
+      error('Unknown algorithm name');
+  end
+end % if
+
+%% Process coplanar triangle pairs. Pass #1:
+% compare the overlap of the bounding boxes
+tMsk = (intMatrix==-1);
+if nnz(tMsk)>0
+  [face1, face2] = find(tMsk);
+  overlap = true;
+  for idim = 1:3
+    v = [V1(face1,idim), V2(face1,idim), V3(face1,idim)];
+    u = [U1(face2,idim), U2(face2,idim), U3(face2,idim)];
+    t1 = min(v,[],2);
+    t2 = max(v,[],2);
+    s1 = min(u,[],2);
+    s2 = max(u,[],2);
+    overlap = overlap & (s1<=t2 & t1<=s2);
+  end
+  % if overlap intMatrix will remain "-1" otherwise it will change to "0"
+  intMatrix(tMsk) = -1*overlap;
+  clear v u t1 t2 s1 s2 overlap
+end
+
+%% Process coplanar triangle pairs. Pass #2:
+% use edge-edge intersections
+tMsk = (intMatrix==-1);
+if nnz(tMsk)>0
+  [face1, face2] = find(tMsk);
+  
+  % repack data prior to function call
+  V(:,:,1)=V1(face1,:); V(:,:,2)=V2(face1,:); V(:,:,3)=V3(face1,:);
+  U(:,:,1)=U1(face2,:); U(:,:,2)=U2(face2,:); U(:,:,3)=U3(face2,:);
+  [intMatrix(tMsk), intSurface2] = TriangleIntersection2D(V, U, ...
+    N1(face1,:), getIntersection, debug);
+  
+  %% Merge surfaces
+  if getIntersection
+    np = size(intSurface.vertices,1);
+    intSurface.vertices = [intSurface.vertices; intSurface2.vertices];
+    intSurface.faces    = [intSurface.faces;    intSurface2.faces+np];
+    intSurface.edges    = [intSurface.edges;    intSurface2.edges+np];
+    if debug
+      np = size(intSurface.vertices,1);
+      assert(max(intSurface.faces(:))<=np, 'Bad surface definition')
+      assert(max(intSurface.edges(:))<=np, 'Bad surface definition')
+    end
+  end
+end
+
+%% Clean up the outputs
+intMatrix = sparse(double(intMatrix));
+if(getIntersection)
+  % make point array unique
+  P = round(intSurface.vertices*PointRoundingTol)/PointRoundingTol;
+  [~,ia,ic] = unique(P,'rows'); % V = P(ia,:) and P = V(ic,:).
+  intSurface.vertices = intSurface.vertices(ia,:);
+  intSurface.faces = ic(intSurface.faces);
+  intSurface.edges = ic(intSurface.edges);
+end
+end % function
+
+%% ========================================================================
+function [iMsk, intSurface] = TriangleIntersection3D_Moller(...
+  V1, V2, V3, N1, d1, dv, ...
+  U1, U2, U3, N2, d2, du, ...
+  getIntersection, debug)
+%TriangleIntersection3D tests if 2 triangles defined in 3D intersect.
+% This is a secondary test following Tomas Moller algorithm
+%
+% INPUTS:
+%   V1, V2, V3, - Nx3 array of surface 1 triangle vertex coordinates
+%   U1, U2, U3, - Nx3 array of surface 2 triangle vertex coordinates
+%   N1, d1      - Nx3 array of surface 1 triangle plane equations N1.X-d1=0
+%   N2, d2      - Nx3 array of surface 2 triangle plane equations N2.X-d2=0
+%   dv          - Nx3 array of distances of surface 1 triangle vertices to surface 2 planes
+%   du          - Nx3 array of distances of surface 2 triangle vertices to surface 1 planes
+%   getIntersection - do we need to output the intersecting surface?
+%      Algorithm is much simpler if we do not.
+%   debug       - In the debugging mode much more extra "sanity check" test
+%      are performed.
+%
+% OUTPUT:
+%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
+%   intSurface - intersection surface
+%
+% ALGORITHM:
+% The input triangles are guaranteed to intersect the line of intersection
+% of the two planes. Furthermore, these intersections form intervals on
+% this line, and the triangles overlap iff these intervals overlap as well.
+% Hence, the last part of  the algorithm computes a parametric equation
+% L(t) of the line of intersection of the two planes, finds the intervals
+% (i.e. scalar intervals on L(t)) for which the line lies inside each
+% triangle and performs a one-dimensional interval overlap test.
+if debug
+  ok = size(N1,2)==3 && size(N2,2)==3 && size(dv,2)==3 && size(du,2)==3 && ...
+    size(V1,2)==3 && size(V2,2)==3 && size(V3,2)==3 && ...
+    size(U1,2)==3 && size(U2,2)==3 && size(U3,2)==3;
+  assert(ok, 'Incorrect array dimensions');
+end
+
+%% create strip down versions of MATLAB cross and dot function
+cross_prod = @(a,b) [...
+  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
+  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
+  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
+dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
+normalize = @(V) bsxfun(@rdivide,V, sqrt(sum(V.^2,2)));
+
+%% Find intervals of surface 1 and 2 triangles
+% compute the scalar intervals on L(t) for which the line lies inside each
+% triangle
+
+% Plane creates two open half-spaces. Find the odd vertex, which:
+% 1) if no or two vertices are on the plane than pick the vertex which is
+%    by itself in its half-space
+% 2) if one vertex is on the plane and the other two occupy the same
+%    half-space than pick the vertex on the plane
+% 3) if one vertex is on the plane and the other two occupy different
+%    half-spaces than pick one of the vertices off the plane
+% Find vertex using a look-up table "lut" with key calculated based on
+% sign of dv and du arrays
+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];
+n = numel(d1);
+rows = (1:n)';
+
+%% order surface 1 triangle vertices
+a1 = lut(sign(dv)*[9; 3; 1] + 14); % calculate the key and call the look-up table
+[b1, c1] = otherDim(a1);
+if debug
+  assert(all(a1>0), 'Something Wrong: triangles are coplanar')
+end
+a1 = sub2ind([n,3],rows,a1); % convert row and column IDs to array indecies
+b1 = sub2ind([n,3],rows,b1);
+c1 = sub2ind([n,3],rows,c1);
+
+%% order surface 2 triangle vertices
+a2 = lut(sign(du)*[9; 3; 1] + 14); % calculate the key and call the look-up table
+[b2, c2] = otherDim(a2);
+if debug
+  assert(all(a2>0), 'Something Wrong: triangles are coplanar')
+end
+a2 = sub2ind([n,3],rows,a2);
+b2 = sub2ind([n,3],rows,b2);
+c2 = sub2ind([n,3],rows,c2);
+
+%% compute direction of L the line of intersection of 2 planes
+% containing 2 triangles. Line L parametric equation: t*D+O=0
+D = cross_prod(N1,N2);    % D must be perpendicular to both N1 and N2
+[~, maxDim] = max(abs(D),[],2); % compute and index to the largest component of D
+if(getIntersection)
+  D = normalize(D);
+  O = zeros(n,3);
+  d = [d1, d2, zeros(n,1)];
+  for r =1:n
+    N = [N1(r,:); N2(r,:); 0, 0, 0];
+    N(3,maxDim(r)) = 1;
+    dd = d(r,:)';
+    O(r,:) = (N\dd)'; %Solve systems of linear equations N*D3 = d for D3
+  end
+  clear N d dd
+end
+
+%% projection of triangle(V1,V2,V3) and triangle(U1,U2,U3) onto intersection line
+% Vp and Up are Nx3 arrays with columns indicating corners of triangles 1 and 2
+if(getIntersection)
+  Vp=[dot_prod(V1-O,D), dot_prod(V2-O,D), dot_prod(V3-O,D)];
+  Up=[dot_prod(U1-O,D), dot_prod(U2-O,D), dot_prod(U3-O,D)];
+else
+  % Project on one of the axis (closest to the intersection line) instead.
+  % Simplified projection is faster and sufficient if we do not need
+  % intersection line
+  idx = sub2ind([n,3],rows,maxDim);
+  Vp = [V1(idx), V2(idx), V3(idx)];
+  Up = [U1(idx), U2(idx), U3(idx)];
+end
+clear V1 V2 V3 U1 U2 U3
+
+%% Calculate surface 1 and 2 triangle intervals
+% t1 and t2 are intersection points of surface 1 with the intersection line
+% t*D+O=0, and s1 & s2 are intersection points of surface 2 with the same
+% line. Tomas Moller algorithm made this section much more complicated
+% trying to avoid divisions. However, I could not detect any speed-up.
+% Operations (ADD: 12; MUL:4 ; DIV:4 )
+t1 = Vp(a1) - (Vp(b1)-Vp(a1)).*dv(a1)./(dv(b1)-dv(a1));
+t2 = Vp(a1) - (Vp(c1)-Vp(a1)).*dv(a1)./(dv(c1)-dv(a1));
+s1 = Up(a2) - (Up(b2)-Up(a2)).*du(a2)./(du(b2)-du(a2));
+s2 = Up(a2) - (Up(c2)-Up(a2)).*du(a2)./(du(c2)-du(a2));
+
+%% Order the intervals as to t1<t2 and s1<s2
+msk = t2<t1; % order t1 and t2 so t1<t2
+t = t1(msk); t1(msk)=t2(msk); t2(msk)=t; % swap
+msk = s2<s1; % order s1 and s2 so s1<s2
+t = s1(msk); s1(msk)=s2(msk); s2(msk)=t; % swap
+
+%% Perform THE final test we were preparying for.
+% It test for the overlap of 2 1D intervals s1->s2 and t1->t2
+iMsk = (s1<t2 & t1<s2);
+
+%% calculate intersection segments
+n = nnz(iMsk);
+if(getIntersection && n>0)
+  % p1 = D*max(t1,s1) + O;    p2 = D*min(t2,s2) + O
+  p1 = bsxfun(@times,D(iMsk,:),max(t1(iMsk),s1(iMsk))) + O(iMsk,:);
+  p2 = bsxfun(@times,D(iMsk,:),min(t2(iMsk),s2(iMsk))) + O(iMsk,:);
+  intSurface.vertices = [p1; p2];
+  intSurface.faces    = [1:n; n+1:2*n; n+1:2*n]';
+  intSurface.edges    = intSurface.faces(:,1:2);
+else
+  intSurface.vertices = [];
+  intSurface.faces    = [];
+  intSurface.edges    = [];
+end % if
+end % function
+
+%% ========================================================================
+function [overlap, intSurface] = TriangleIntersection2D(V, U, N, ...
+  getIntersection, debug)
+% Triangles V(V0,V1,V2) and U(U0,U1,U2) are are coplanar. Do they overlap?
+% INPUTS:
+% N - array(n,3) of surface normals where V(i,:,:) and U(i,:,:) are on the same plane
+% V - array(n,3,3) (nFace x 3 dimensions x 3 vertices) of surface #1 vertices
+% U - array(n,3,3) (nFace x 3 dimensions x 3 vertices) of surface #2 vertices
+%
+% OUTPUT:
+%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
+%   intSurface - intersection surface
+
+
+%  * parameters: vertices of triangle 1: V0,V1,V2
+%  *             vertices of triangle 2: U0,U1,U2
+%  * result    : returns 1 if the triangles intersect, otherwise 0
+
+%% Constants needed for creating a mesh based on 3 to 6 points in a circle
+tri_mesh{6}  = [1 2 6; 2 4 6; 2 3 4; 4 5 6];
+tri_mesh{5}  = [1 2 3; 1 3 4; 4 5 1];
+tri_mesh{4}  = [1 2 3; 1 3 4];
+tri_mesh{3}  = 1:3;
+vertices = [];
+faces    = [];
+pairs    = [];  % each row corresponds to pair of faces. match row number with face number
+nVert    = 0;
+
+%% use edge-edge intersections
+overlap = false(size(N,1),1);
+i1Idx = [1 1 1 2 2 2 3 3 3];
+i2Idx = [3 3 3 1 1 1 2 2 2];
+j1Idx = [1 2 3 1 2 3 1 2 3];
+j2Idx = [3 1 2 3 1 2 3 1 2];
+for row = 1:size(N,1)
+  % When it is necesary to project 3D plane on 2D, dIdx will be the optimal
+  % dimensions to use.
+  [~, a] = max(abs(N(row,:))); 
+  [b, c] = otherDim(a); 
+  dIdx = [b, c]; 
+  order = [];
+
+  %% test all edges of triangle 1 against the edges of triangle 2
+  % triangles overlap if edges cross
+  [edgeMat, P] = EdgesIntersect3D(...
+    squeeze(V(row,:,i1Idx))',squeeze(V(row,:,i2Idx))', ...
+    squeeze(U(row,:,j1Idx))',squeeze(U(row,:,j2Idx))');
+  overlap(row) = any(edgeMat);
+  if ~getIntersection && overlap(row), continue; end
+  
+  if ~overlap(row)
+    %% project onto an axis-aligned plane, that maximizes the area
+    % of the triangles, compute indices: dIdx which correspond to 2 smallest N1
+    % components.
+    V2d = [V(row,dIdx,1); V(row,dIdx,2); V(row,dIdx,3)]; % each row is a 2D vertex
+    U2d = [U(row,dIdx,1); U(row,dIdx,2); U(row,dIdx,3)];
+    
+    %% test if tri1 is totally contained in tri2 or vice varsa
+    if PointInTriangle2D(V2d(1,:), U2d) % tri1 is totally contained in tri2
+      overlap(row) = true;
+      order = 1:3;
+    elseif PointInTriangle2D(U2d(1,:), V2d) % tri2 is totally contained in tri1
+      overlap(row) = true;
+      order = 4:6;
+    end
+    if overlap(row) && ~getIntersection, continue; end
+    clear V2d U2d
+  end
+  
+  %% Build the intersection surface
+  if getIntersection && overlap(row)
+    %Assemble all the points which might be needed for desining
+    %intersection polygon: Intersection points and points from triangle 1
+    %and 2
+    points   = [P(edgeMat,:); squeeze(V(row,:,:))'; squeeze(U(row,:,:))'];
+    if isempty(order) % when one tri is totally contained in the other tri then order is set
+      order = IntersectionPolygon(edgeMat>0, points, dIdx, debug);
+      if isempty(order), continue; end
+    end
+    nPoint   = length(order);    % how many points will be added?
+    nFace    = nPoint-2;         % how many faces will be added?
+    vertices = [vertices; points(order,:)]; %#ok<*AGROW>
+    faces    = [faces; nVert+tri_mesh{nPoint} ];
+    pairs    = [pairs; row+zeros(nFace,1)];  % each row corresponds to pair of faces. match row number with face number
+    nVert    = nVert + nPoint;
+    if debug
+      assert(max(faces(:))<=size(vertices,1), 'Bad surface definition')
+    end
+  end
+end % for
+
+%% Prepare outputs
+intSurface.vertices = vertices;
+intSurface.faces    = faces;
+if isempty(faces)
+  intSurface.edges = [];
+else
+  intSurface.edges = [faces(:,1:2); faces(:,2:3); faces(:,[1,3])];
+end
+end % function
+
+%% ========================================================================
+function polygon = IntersectionPolygon(edgeMat, points, dIdx, debug)
+% edgeMat is an edge intersection matrix with 3 rows for edges between
+% the points 1-3, 1-2, & 2-3 of the triangle 1 and 3 columns for the same
+% edges of the triangle 2. If 2 edges intersect a point of intersection
+% is calculated and stored in array "points" followed by points of the
+% triangles 1 & 2.  This function calculates the polygon of the intersection
+% between 2 triangles.
+
+persistent orderLUT verified
+if isempty(orderLUT) || isempty(orderLUT{3})
+  % This pre-calculated look-up table is used to quickly look up the order of
+  % the vertices in array "points" which make up polygon of the intersection
+  % between 2 triangles. A unique key is calculated for each edgeMat using
+  % dot product between edgeMat(:) and [256 128 64 32 16 8 4 2 1], which is
+  % used to look up point order around the polygon. Negative numbers in the
+  % LUT indicate values which were not observed yet so they were not
+  % independently verified.
+  % reshape(sprintf('%09s',dec2base(key, 2)),3,3) will convert from the key
+  % to matrix.
+  OrderLUT = zeros(432,1);  
+  OrderLUT(003) = 127;
+  OrderLUT(005) = 128;
+  OrderLUT(006) = 126;
+  OrderLUT(009) = 124;
+  OrderLUT(010) = 1427;
+  OrderLUT(012) = 1428;
+  OrderLUT(017) = 1427;
+  OrderLUT(018) = 124;
+  OrderLUT(020) = 1426;
+  OrderLUT(024) = 127;
+  OrderLUT(027) = 1243;
+  OrderLUT(029) = 12438;
+  OrderLUT(030) = 12034;
+  OrderLUT(033) = 1428;
+  OrderLUT(034) = 1426;
+  OrderLUT(036) = 124;
+  OrderLUT(040) = 128;
+  OrderLUT(043) = 21834;
+  OrderLUT(045) = 1243;
+  OrderLUT(046) = 21349;
+  OrderLUT(048) = 126;
+  OrderLUT(051) = 12340;
+  OrderLUT(053) = 12943;
+  OrderLUT(054) = 1243;
+  OrderLUT(065) = 125;
+  OrderLUT(066) = 1527;
+  OrderLUT(068) = 1825;
+  OrderLUT(072) = 123;
+  OrderLUT(080) = 1327;
+  OrderLUT(083) = 15234;
+  OrderLUT(085) = -15234;
+  OrderLUT(086) = -15243;
+  OrderLUT(090) = 13247;
+  OrderLUT(092) = -13247;
+  OrderLUT(096) = 1328;
+  OrderLUT(099) = 152834;
+  OrderLUT(101) = 15234;
+  OrderLUT(102) = 152349;
+  OrderLUT(106) = 132847;
+  OrderLUT(108) = 13247;
+  OrderLUT(114) = 102347;
+  OrderLUT(116) = -13247;
+  OrderLUT(129) = 1527;
+  OrderLUT(130) = 125;
+  OrderLUT(132) = 1526;
+  OrderLUT(136) = 1327;
+  OrderLUT(139) = 15243;
+  OrderLUT(141) = 152438;
+  OrderLUT(142) = 152034;
+  OrderLUT(144) = 123;
+  OrderLUT(153) = 12347;
+  OrderLUT(156) = 123047;
+  OrderLUT(160) = 1326;
+  OrderLUT(163) = -152043;
+  OrderLUT(165) = 13247;
+  OrderLUT(166) = 15234;
+  OrderLUT(169) = -182347;
+  OrderLUT(172) = 193247;
+  OrderLUT(177) = -132047;
+  OrderLUT(180) = 13247;
+  OrderLUT(192) = 127;
+  OrderLUT(195) = 1243;
+  OrderLUT(197) = 12438;
+  OrderLUT(198) = 12034;
+  OrderLUT(202) = 12364;
+  OrderLUT(204) = 123648;
+  OrderLUT(209) = 21364;
+  OrderLUT(212) = -21364;
+  OrderLUT(216) = 1243;
+  OrderLUT(225) = -124638;
+  OrderLUT(226) = 120364;
+  OrderLUT(232) = 12438;
+  OrderLUT(238) = 124356;
+  OrderLUT(240) = 12034;
+  OrderLUT(245) = -214356;
+  OrderLUT(257) = 1528;
+  OrderLUT(258) = 1526;
+  OrderLUT(260) = 125;
+  OrderLUT(264) = 1328;
+  OrderLUT(267) = -152438;
+  OrderLUT(269) = 15243;
+  OrderLUT(270) = -152943;
+  OrderLUT(272) = 1326;
+  OrderLUT(275) = 152340;
+  OrderLUT(277) = 152943;
+  OrderLUT(278) = 15243;
+  OrderLUT(281) = 182347;
+  OrderLUT(282) = -103247;
+  OrderLUT(288) = 123;
+  OrderLUT(297) = 12347;
+  OrderLUT(298) = -123947;
+  OrderLUT(305) = 123947;
+  OrderLUT(306) = 12347;
+  OrderLUT(320) = 128;
+  OrderLUT(323) = 21834;
+  OrderLUT(325) = 1243;
+  OrderLUT(326) = 21349;
+  OrderLUT(330) = -123648;
+  OrderLUT(332) = 12364;
+  OrderLUT(337) = 183642;
+  OrderLUT(340) = -129364;
+  OrderLUT(344) = 21834;
+  OrderLUT(350) = -124365;
+  OrderLUT(353) = 12463;
+  OrderLUT(354) = 136492;
+  OrderLUT(360) = 1243;
+  OrderLUT(368) = 12943;
+  OrderLUT(371) = 126543;
+  OrderLUT(384) = 126;
+  OrderLUT(387) = 12340;
+  OrderLUT(389) = 12943;
+  OrderLUT(390) = 1243;
+  OrderLUT(394) = -103642;
+  OrderLUT(396) = 129364;
+  OrderLUT(401) = 123640;
+  OrderLUT(404) = 12364;
+  OrderLUT(408) = 12340;
+  OrderLUT(413) = 215643;
+  OrderLUT(417) = -136492;
+  OrderLUT(418) = 12463;
+  OrderLUT(424) = 13492;
+  OrderLUT(427) = -213456;
+  OrderLUT(432) = 1342;
+  
+  % Convert to more convinient format
+  orderLUT = cell(size(OrderLUT));
+  for i = 1:size(OrderLUT,1)
+    polygon = abs(OrderLUT(i));
+    if polygon>0
+      polygon = num2str(polygon)-48; % Convert from a single number to array of digits
+      polygon(polygon==0) = 10;      % 0 stands for 10
+      orderLUT{i} = polygon;
+    end
+  end
+  % Negative numbers in the LUT indicate values which were not observed yet
+  % so they were not independently verified.
+  verified = OrderLUT>0;
+  clear OrderLUT
+end
+
+%% Calculate unique key for each edgeMat configuration
+key = dot(1*edgeMat(:)', [256 128 64 32 16 8 4 2 1]);
+assert(key<=432, 'Error: in IntersectionPolygon: key is out of bound');
+
+%% Look up the point order around the polygon
+polygon = orderLUT{key};
+if (isempty(polygon))
+  return
+end
+
+%% in a rare case of 2 intersections there is ambiguity if one or two
+% vertices of the triangle lay inside the other triangle. OrderLUT stores
+% only the single vertex cases.
+nx = nnz(edgeMat(:));
+if nx==2
+  pList = polygon;       % list of vertices to check
+  pList(pList<=nx) = []; % keep only the triangle points of the polygon
+  flip = false;    % was there a flip from single vertex to vertices case?
+  for ip = 1:length(pList)
+    p = pList(ip);                 % point to check
+    t = floor((p-nx-1)/3);         % does it belong to triangle 0 or 1 (actually 1 or 2)
+    tri = (1:3) + nx + 3*abs(1-t); % Points belonging to the other triangle
+    if ~PointInTriangle2D(points(p,dIdx), points(tri,dIdx))
+      d = nx+t*3;    % offset
+      % "p-d" is vertex number of point just tested: 1, 2, or 3. "b, c" are
+      % the other 2 vertices
+      [b, c] = otherDim(p-d);
+      polygon = [polygon(polygon~=p), b+d, c+d]; % remove i2 and add i0 and i1
+      flip = true;
+    end
+  end
+  if flip
+    % if ther were any flips than use existing codes to figure out the
+    % order of the points around the polygon
+    DT = delaunayTriangulation(points(polygon,dIdx));
+    idx = freeBoundary(DT)';
+    idx(2,:) = [];
+    polygon = polygon(idx);
+  end
+end
+
+%% Check to duplicate points
+tol = 1e6;
+P = round(points(polygon,:)*tol)/tol;
+[~,ia] = unique(P,'rows'); % V = P(ia,:) and P = V(ic,:).
+polygon = polygon(sort(ia));
+
+%% Test the results using more expensive function
+doPlot = (~verified(key));
+if debug && length(polygon)>3
+  DT = delaunayTriangulation(points(polygon,dIdx));
+  idx = freeBoundary(DT)';
+  idx(2,:) = [];
+  k = max(abs(diff(idx)));
+  %doPlot = (k>1 && k<(length(idx)-1)) || (~verified(key));
+  assert(k==1 || k==(length(idx)-1), 'Two triangle intersection polygon is not convex')
+end
+if debug && doPlot % plot the interesting cases
+  PlotTwoTriangles(points, polygon, 'm')
+  title(sprintf('key = %i', key));
+end 
+
+end % function
+
+%% ========================================================================
+function PlotTwoTriangles(points, polygon, color)
+% Plotting function used for debugging
+nx = size(points,1)-6;
+d = (max(points,[],1)-min(points,[],1))/200;
+figure(2)
+clf
+hold on
+line( points(nx+(1:2),1), points(nx+(1:2),2), points(nx+(1:2),3), 'Color', 'g');
+line( points(nx+(2:3),1), points(nx+(2:3),2), points(nx+(2:3),3), 'Color', 'g');
+line( points(nx+[1,3],1), points(nx+[1,3],2), points(nx+[1,3],3), 'Color', 'g');
+line( points(nx+(4:5),1), points(nx+(4:5),2), points(nx+(4:5),3), 'Color', 'b');
+line( points(nx+(5:6),1), points(nx+(5:6),2), points(nx+(5:6),3), 'Color', 'b');
+line( points(nx+[4,6],1), points(nx+[4,6],2), points(nx+[4,6],3), 'Color', 'b');
+plot3( points(:,1), points(:,2), points(:,3), 'm.');
+if (length(polygon)>2)
+  idx = polygon([1:end, 1]);
+  plot3( points(idx,1), points(idx,2),points(idx,3), 'Color', color, 'LineWidth', 1);
+end
+for i = 1:nx+6
+  text(points(i,1)+d(1), points(i,2)+d(2), points(i,3), num2str(i))
+end
+
+end % function
+
+%% ========================================================================
+function [intersect, X] = EdgesIntersect3D(V1,V2, U1,U2)
+%EdgesIntersectPoint3D calculates point of intersection of 2 coplanar
+% segments in 3D
+%
+% INPUTS:
+%   V1,V2 - 1 x 3 coordinates of endpoints of edge 1
+%   U1,U2 - 1 x 3 coordinates of endpoints of edge 2
+% OUTPUT:
+%   X - 1 x 3 coordinates of the intersection point
+A = V2-V1;
+B = U1-U2;
+C = U1-V1;
+%% Solve system of equations [A,B,1] * [d;e;0] = C for d and e
+det3 = @(a,b) ... % determinant of a matrix with columns: [a, b, 1]
+  a(:,1).*b(:,2)-a(:,3).*b(:,2) + ...
+  a(:,2).*b(:,3)-a(:,2).*b(:,1) + ...
+  a(:,3).*b(:,1)-a(:,1).*b(:,3);
+f=det3(A,B); % https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems
+t=det3(C,B)./f; % use Cramer's rule
+s=det3(A,C)./f;
+intersect = (t>=0 & t<=1 & s>=0 & s<=1);
+X = V1 + bsxfun(@times,A,t);
+end % function
+
+%% ========================================================================
+function inside = PointInTriangle2D(V1, U)
+% check if V1 is inside triangle U (U1,U2,U3)
+% Algorithm is checking on which side of the half-plane created by the
+% edges the point is. It uses sign of determinant to calculate orientation
+% of point triplets.
+% INPUTS:
+%   V1 - 1 x 2 coordinates of a point
+%   U  - 3 x 2 coordinates of endpoints of 3 edges of a triangle
+% OUTPUT:
+%   inside - a boolean or boolean array
+det2 = @(A,B,C) (A(:,1)-C(:,1))*(B(:,2)-C(:,2)) - (B(:,1)-C(:,1))*(A(:,2)-C(:,2));
+b1 = (det2(U(1,:), U(2,:), V1) > 0);
+b2 = (det2(U(2,:), U(3,:), V1) > 0);
+b3 = (det2(U(3,:), U(1,:), V1) > 0);
+inside = ((b1 == b2) & (b2 == b3)); % inside if same orientation for all 3 edges
+end % function
+
+%% ========================================================================
+function [b, c] = otherDim(a)
+% return set [1 2 3] without k
+b = mod(a+1,3)+1;  % b and c are vertices which are on the same side of the plane
+c = 6-a-b;         % a+b+c = 6
+end
+
+
+%% ========================================================================
+function overlap = TriangleIntersection3D_Rapid( v1, v2, v3, u1, u2, u3, n1, n2 )
+%TriangleIntersection3D tests if 2 triangles defined in 3D intersect.
+%
+% INPUTS:
+%   v1, v2, v3, - Nx3 array of surface 1 triangle vertex coordinates
+%   u1, u2, u3, - Nx3 array of surface 2 triangle vertex coordinates
+%   n1, n2      - Nx3 array of surface 1 & 2 triangle plane normals. Those
+%      are optional and if provided than the first 2 steps of the algorithm
+%      (which are equivalent to first 2 steps of Moller algorithm) will be 
+%      skipped.
+%
+% OUTPUT:
+%   iMsk - N x 1 intersection boolean mask marking which triangles overlap
+%
+% ALGORITHM:
+%   translated from the UNC-CH V-Collide RAPID code
+%    https://wwwx.cs.unc.edu/~geom/papers/COLLISION/vcol.pdf
+
+global V1 V2 V3 U1 U2 U3
+
+cross_prod = @(a,b) [...
+  a(:,2).*b(:,3)-a(:,3).*b(:,2), ...
+  a(:,3).*b(:,1)-a(:,1).*b(:,3), ...
+  a(:,1).*b(:,2)-a(:,2).*b(:,1)];
+
+%% shift t1 and t2 by p1#
+V1 = zeros(size(v1));
+V2 = v2-v1;
+V3 = v3-v1;
+U1 = u1-v1;
+U2 = u2-v1;
+U3 = u3-v1;
+clear v1 v2 v3 u1 u2 u3
+
+if(nargin<7)
+  %% now begin the series of tests
+  n1 = cross_prod( V2-V1, V3-V1 ); % face normals
+  n2 = cross_prod( U2-U1, U3-U1 ); % face normals
+end
+  
+%% test the face normals
+overlap = project6(n1) & project6(n2);
+V1 = V1(overlap,:);
+V2 = V2(overlap,:);
+V3 = V3(overlap,:);
+U1 = U1(overlap,:);
+U2 = U2(overlap,:);
+U3 = U3(overlap,:);
+n1 = n1(overlap,:);
+n2 = n2(overlap,:);
+
+%% compute triangle edges
+e1 = V2-V1;
+e2 = V3-V2;
+e3 = V1-V3;
+f1 = U2-U1;
+f2 = U3-U2;
+f3 = U1-U3;
+
+%% run more tests
+overlap2 = project6(cross_prod(e1, f1));
+overlap2 = project6(cross_prod(e1, f2)) & overlap2;
+overlap2 = project6(cross_prod(e1, f3)) & overlap2;
+overlap2 = project6(cross_prod(e2, f1)) & overlap2;
+overlap2 = project6(cross_prod(e2, f2)) & overlap2;
+overlap2 = project6(cross_prod(e2, f3)) & overlap2;
+overlap2 = project6(cross_prod(e3, f1)) & overlap2;
+overlap2 = project6(cross_prod(e3, f2)) & overlap2;
+overlap2 = project6(cross_prod(e3, f3)) & overlap2;
+overlap2 = project6(cross_prod(e1, n1)) & overlap2;
+overlap2 = project6(cross_prod(e2, n1)) & overlap2;
+overlap2 = project6(cross_prod(e3, n1)) & overlap2;
+overlap2 = project6(cross_prod(f1, n2)) & overlap2;
+overlap2 = project6(cross_prod(f2, n2)) & overlap2;
+overlap2 = project6(cross_prod(f3, n2)) & overlap2;
+overlap(overlap) = overlap2;
+end
+
+%% ========================================================================
+function pass = project6( p )
+% project all 6 vertices of both triangles onto vector p and check if two
+% projections overlap
+global V1 V2 V3 U1 U2 U3
+dot_prod = @(a,b) a(:,1).*b(:,1)+a(:,2).*b(:,2)+a(:,3).*b(:,3);
+%% Project vertices of triangle 1 and find the bounds min1 and max1
+P = [dot_prod(p, V1), dot_prod(p, V2), dot_prod(p, V3)];
+max1 = max(P,[],2);
+min1 = min(P,[],2);
+%% Project vertices of triangle 2 and find the bounds min1 and max1
+P = [dot_prod(p, U1), dot_prod(p, U2), dot_prod(p, U3)];
+max2 = max(P,[],2);
+min2 = min(P,[],2);
+%% Compare the bounds to see if they overlap
+pass = (( min1 < max2 ) & ( min2 < max1 )) | ~dot_prod(p, p);
+end
\ No newline at end of file