1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
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
|