function [cutpos,cutvalue,facedata,elemid,nodeid]=qmeshcut(elem,node,value,cutat,varargin)
%
% [cutpos,cutvalue,facedata,elemid,nodeid]=qmeshcut(elem,node,value,cutat)
%
% fast tetrahedral mesh slicer
%
% author:Qianqian Fang, <q.fang at neu.edu>
%
% input: 
%   elem: integer array with dimensions of NE x 4, each row contains
%         the indices of all the nodes for each tetrahedron
%   node: node coordinates, 3 columns for x, y and z respectively
%   value: a scalar array with the length of node numbers, can have 
%          multiple columns 
%   cutat: cutat can have different forms:
%          if cutat is a 3x3 matrix, it defines a plane by 3 points: 
%                 cutat=[x1 y1 z1;x2 y2 z2;x3 y3 z3]
%          if cutat is a vector of 4 element, it defines a plane by
%                 a*x+b*y+c*z+d=0  and cutat=[a b c d]
%          if cutat is a single scalar, it defines an isosurface 
%                 inside the mesh at value=cutat
%          if cutat is a string, it defines an implicit surface
%                 at which the cut is made. it must has form expr1=expr2
%                 where expr1 expr2 are expressions made of x,y,z,v and
%                 constants
%          if cutat is a cell in the form of {'expression',scalar}, 
%                 the expression will be evaluated at each node to 
%                 produce a new value, then an isosurface is produced 
%                 at the levelset where new value=scalar; the 
%                 expression can contain constants and x,y,z,v
%
% output:
%   cutpos: all the intersections of mesh edges by the cutat
%           cutpos is similar to node, containing 3 columns for x/y/z
%   cutvalue: interpolated values at the intersections, with row number
%           being the num. of the intersections, column number being the 
%           same as "value".
%   facedata: define the intersection polygons in the form of patch "Faces"
%   elemid: the index of the elem in which each intersection polygon locates
%   nodeid: 3 column array, first two columns are the node indices that
%           each intersection position is interpolated between, and the
%           last column is a weight (0-1) for the first node (that for
%           the 2nd node is 1-weight).
%
%   without any output, qmeshcut generates a cross-section plot
%
% the outputs of this subroutine can be easily plotted using 
%
%  % if value has a length of node:
%     patch('Vertices',cutpos,'Faces',facedata,'FaceVertexCData',cutvalue,'FaceColor','interp');
%
%  % if value has a length of elem:
%     patch('Vertices',cutpos,'Faces',facedata,'CData',cutvalue,'FaceColor','flat');
%
% -- this function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
%

% get the coefficients of the cutat equation: ax+by+cz+d=0
if(nargin<4)
    error('qmeshcut requires at least 4 inputs');
end
if(size(value,1)~=size(node,1) && size(value,1)~=size(elem,1) && ~isempty(value))
    error('the length of value must be either that of node or elem');
end
if(isempty(value))
    cutvalue=[];
end
if(ischar(cutat) || (iscell(cutat) && length(cutat)==2 && ischar(cutat{1})))
    x=node(:,1);
    y=node(:,2);
    z=node(:,3);
    if(ischar(cutat))
        expr=regexp(cutat,'(.+)=(.+)','tokens','once'); %regexp(cutat,'=','split');
        if(length(expr)~=2) error('single expression must contain a single "=" sign'); end
        dist=eval(expr{1})-eval(expr{2});
    else
        dist=eval(cutat{1})-cutat{2};
    end
    if(all(dist<=0))
        asign=-double(dist<0);
        asign(asign==0)=1;
    else
        asign=double(dist>0);
        asign(asign==0)=-1;
    end
elseif(numel(cutat)==9 || numel(cutat)==4)
    if(numel(cutat)==9)
        [a,b,c,d]=getplanefrom3pt(cutat);
    else
        coeff=num2cell(cutat(:));
        [a,b,c,d]=deal(coeff{:});
    end

    % compute which side of the cutat for all nodes in the mesh
    co=repmat([a b c],size(node,1),1);
    dist=sum( (co.*node)' )+d;
    asign=dist;
    asign(find(asign>=0))=1;
    asign(find(asign<0))=-1;
else
    if(size(value,1)~=size(node,1))
        error('must use nodal value list when cutting mesh at an isovalue');
    end
    dist=value-cutat;
    if(all(dist<=0))
        asign=-double(dist<0);
        asign(asign==0)=1;
    else
        asign=double(dist>0);
        asign(asign==0)=-1;
    end
end

% get all the edges of the mesh
esize=size(elem,2);
if(esize==4)
    edges=[elem(:,[1,2]);elem(:,[1,3]);elem(:,[1,4]);
           elem(:,[2,3]);elem(:,[2,4]);elem(:,[3,4])];
elseif(esize==3)
    edges=[elem(:,[1,2]);elem(:,[1,3]);elem(:,[2,3])];
elseif(esize==10)
    edges=[elem(:,[1,5]);elem(:,[1,8]);elem(:,[1,7]);
           elem(:,[2,5]);elem(:,[2,6]);elem(:,[2,9]);
	   elem(:,[3,6]);elem(:,[3,7]);elem(:,[3,10]);
	   elem(:,[4,8]);elem(:,[4,9]);elem(:,[4,10])];
end

% find all edges with two ends at the both sides of the plane
edgemask=sum(asign(edges),2);
cutedges=find(edgemask==0);
%edgemask=prod(asign(edges)');
%cutedges=find(edgemask<0);

% calculate the distances of the two nodes, and use them as interpolation weight 
cutweight=dist(edges(cutedges,:));
totalweight=diff(cutweight');

%caveat: if an edge is co-planar to the cutat, then totalweight will be 0
%        and dividing zero will cause trouble for cutweight

cutweight=abs(cutweight./repmat(totalweight(:),1,2));

% calculate the cross-cut position and the interpolated values

nodeid=edges(cutedges,:);
nodeid(:,3)=cutweight(:,2);

cutpos=node(edges(cutedges,1),:).*repmat(cutweight(:,2),[1 3])+...
       node(edges(cutedges,2),:).*repmat(cutweight(:,1),[1 3]);
if(size(value,1)==size(node,1))
  if(iscell(cutat) || ischar(cutat) || numel(cutat)==9 || numel(cutat)==4)
      cutvalue=value(edges(cutedges,1),:).*repmat(cutweight(:,2),[1 size(value,2)])+...
               value(edges(cutedges,2),:).*repmat(cutweight(:,1),[1 size(value,2)]);
  elseif(numel(cutat)==1)
      cutvalue=ones(size(cutpos,1),1)*cutat;
  end
end 
% organize all cross-cuts into patch facedata format

emap=zeros(size(edges,1),1);
emap(cutedges)=1:length(cutedges);
if(esize==10)
        emap=reshape(emap,[size(elem,1),12]); % 10-node element
else
	emap=reshape(emap,[size(elem,1),esize*(esize-1)/2]); % C^n_2
end
faceid=find(any(emap,2)==1);
facelen=length(faceid);

% cross-cuts can only be triangles or quadrilaterals for tetrahedral mesh
% (co-plannar mesh needs to be considered)

etag=sum(emap>0,2); % emap && etag are of length size(elem,1)

if(esize==3)  % surface mesh cut by a plane
	linecut=find(etag==2);
	lineseg=emap(linecut,:)';
	facedata=reshape(lineseg(find(lineseg)),[2,length(linecut)])';
    elemid=linecut;
    if(size(value,1)==size(elem,1) && ~exist('cutvalue','var'))
        cutvalue=value(elemid,:);
    end
	return;
end

tricut=find(etag==3);
quadcut=find(etag==4);
elemid=[tricut(:);quadcut(:)];
if(size(value,1)==size(elem,1) && ~exist('cutvalue','var'))
    cutvalue=value(elemid,:);
end
% fast way (vector-form) to get all triangles

tripatch=emap(tricut,:)';
tripatch=reshape(tripatch(find(tripatch)),[3,length(tricut)])';

% fast way to get all quadrilaterals in convexhull form (avoid using
% convhulln)

quadpatch=emap(quadcut,:)';
quadpatch=reshape(quadpatch(find(quadpatch)),[4,length(quadpatch)])';

% combine the two sets to create the final facedata
% using the matching-tetrahedra algorithm as shown in 
% https://visualization.hpc.mil/wiki/Marching_Tetrahedra

facedata=[tripatch(:,[1 2 3 3]); quadpatch(:,[1 2 4 3])];

% plot your results with the following command

if(nargout==0)
  patch('Vertices',cutpos,'Faces',facedata,'FaceVertexCData',cutvalue,'facecolor','interp',varargin{:});
end
