--- 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