Code covered by the BSD License

geom3d

David Legland (view profile)

19 Jun 2009 (Updated )

Library to handle 3D geometric primitives: create, intersect, display, and make basic computations

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

meshReduce(nodes, varargin)
```function varargout = meshReduce(nodes, varargin)
%MESHREDUCE Merge coplanar faces of a polyhedral mesh
%
%   Note: deprecated, should use "mergeCoplanarFaces" instead
%
%   [NODES FACES] = meshReduce(NODES, FACES)
%   [NODES EDGES FACES] = meshReduce(NODES, EDGES, FACES)
%   NODES is a set of 3D points (as a Nn-by-3 array),
%   and FACES is one of:
%   - a Nf-by-X array containing vertex indices of each face, with each
%   face having the same number of vertices,
%   - a Nf-by 1 cell array, each cell containing indices of a face.
%   The function groups faces which are coplanar and contiguous, resulting
%   in a "lighter" mesh. This can be useful for visualizing binary 3D
%   images for example.
%
%   FACES = meshReduce(..., PRECISION)
%   Adjust the threshold for deciding if two faces are coplanar or
%   parallel. Default value is 1e-14.
%
%   Example
%   [n e f] = createCube;
%   figure; drawMesh(n, f); view(3); axis equal;
%   f2 = meshReduce(n, f);
%   figure; drawMesh(n, f2); view(3); axis equal;
%
%   meshes3d, mergeCoplanarFaces
%

% ------
% Author: David Legland
% e-mail: david.legland@grignon.inra.fr
% Created: 2006-07-05
% Copyright 2006 INRA - CEPIA Nantes - MIAJ (Jouy-en-Josas).

% 20/07/2006 add tolerance for coplanarity test
% 21/08/2006 fix small bug due to difference of methods to test
%   coplanaritity, sometimes resulting in 3 points of a face not coplanar !
%   Also add control on precision
% 14/08/2007 rename minConvexHull->meshReduce, and extend to non convex
%   shapes
% 2011-01-14 code clean up
% 2013-02-22 deprecate and rename to mergeCoplanarFaces

warning('MatGeom:meshes3d:deprecated', ...
'Function ''meshReduce'' is deprecated, should use ''mergeCoplanarFaces'' instead');

%% Process input arguments

% set up precision
acc = 1e-14;
if ~isempty(varargin)
var = varargin{end};
if length(var)==1
acc = var;
varargin(end) = [];
end
end

% extract faces and edges
if length(varargin)==1
faces = varargin{1};
else
faces = varargin{2};
end

%% Initialisations

% number of faces
Nn = size(nodes, 1);
Nf = size(faces, 1);

% compute number of vertices of each face
Fn = ones(Nf, 1)*size(faces, 2);

% compute normal of each faces
normals = faceNormal(nodes, faces);

% initialize empty faces and edges
faces2  = cell(0, 1);
edges2  = zeros(0, 2);

% Processing flag for each face
% 1: face to process, 0: already processed
% in the beginning, every triangle face need to be processed
flag = ones(Nf, 1);

%% Main iteration

% iterate on each  face
for f = 1:Nf

% check if face was already performed
if ~flag(f)
continue;
end

% indices of faces with same normal
ind = find(abs(vectorNorm3d(cross(repmat(normals(f, :), [Nf 1]), normals)))<acc);
%ind = ind(ind~=i);

% keep only coplanar faces (test coplanarity of points in both face)
ind2 = false(size(ind));
for j = 1:length(ind)
ind2(j) = isCoplanar(nodes([faces(f,:) faces(ind(j),:)], :), acc);
end
ind2 = ind(ind2);

% compute edges of all faces in the plane
planeEdges  = zeros(sum(Fn(ind2)), 2);
pos  = 1;
for i=1:length(ind2)
face = faces(ind2(i), :);
faceEdges = sort([face' face([2:end 1])'], 2);
planeEdges(pos:sum(Fn(ind2(1:i))), :) = faceEdges;
pos = sum(Fn(ind2(1:i)))+1;
end
planeEdges = unique(planeEdges, 'rows');

% relabel plane edges, and find connected components
[planeNodes, I, J] = unique(planeEdges(:)); %#ok<ASGLU>
planeEdges2 = reshape(J, size(planeEdges));
component   = grLabel(nodes(planeNodes, :), planeEdges2);

% compute degree (number of adjacent faces) of each edge.
Npe = size(planeEdges, 1);
edgesDegree = zeros(Npe, 1);
for i = 1:length(ind2)
face = faces(ind2(i), :);
faceEdges = sort([face' face([2:end 1])'], 2);
for j = 1:size(faceEdges, 1)
indEdge = find(sum(ismember(planeEdges, faceEdges(j,:)),2)==2);
edgesDegree(indEdge) = edgesDegree(indEdge)+1;
end
end

% extract unique edges and nodes of the plane
planeEdges  = planeEdges(edgesDegree==1, :);
planeEdges2 = planeEdges2(edgesDegree==1, :);

% find connected component of each edge
planeEdgesComp = zeros(size(planeEdges, 1), 1);
for e = 1:size(planeEdges, 1)
planeEdgesComp(e) = component(planeEdges2(e, 1));
end

% iterate on connected faces
for c=1:max(component)

% convert to chains of nodes
loops = graph2Contours(nodes, planeEdges(planeEdgesComp==c, :));

% add a simple Polygon for each loop
facePolygon = loops{1};
for l = 2:length(loops)
facePolygon = [facePolygon, NaN, loops{l}]; %#ok<AGROW>
end
faces2{length(faces2)+1, 1}  = facePolygon;

edges2 = unique([edges2; planeEdges], 'rows');
end

% mark processed faces
flag(ind2) = 0;
end

% select only nodes which appear in at least one edge
indNodes = unique(edges2(:));

% for each node, compute index of corresponding new node (or 0 if dropped)
refNodes = zeros(Nn, 1);
for i=1:length(indNodes)
refNodes(indNodes(i)) = i;
end

% changes indices of nodes in edges2 array
for i=1:length(edges2(:))
edges2(i) = refNodes(edges2(i));
end

% changes indices of nodes in faces2 array
for f=1:length(faces2)
face = faces2{f};
for i=1:length(face)
if ~isnan(face(i))
face(i) = refNodes(face(i));
end
end
faces2{f} = face;
end

% keep only boundary nodes
nodes2 = nodes(indNodes, :);

%% Process output arguments

if nargout == 1
varargout{1} = faces2;
elseif nargout == 2
varargout{1} = nodes2;
varargout{2} = faces2;
elseif nargout==3
varargout{1} = nodes2;
varargout{2} = edges2;
varargout{3} = faces2;
end

function labels = grLabel(nodes, edges)
%GRLABEL associate a label to each connected component of the graph
%   LABELS = grLabel(NODES, EDGES)
%   Returns an array with as many rows as the array NODES, containing index
%   number of each connected component of the graph. If the graph is
%   totally connected, returns an array of 1.
%
%   Example
%       nodes = rand(6, 2);
%       edges = [1 2;1 3;4 6];
%       labels = grLabel(nodes, edges);
%   labels =
%       1
%       1
%       1
%       2
%       3
%       2
%
%   getNeighbourNodes
%
%
% ------
% Author: David Legland
% e-mail: david.legland@grignon.inra.fr
% Created: 2007-08-14,    using Matlab 7.4.0.287 (R2007a)
% Copyright 2007 INRA - BIA PV Nantes - MIAJ Jouy-en-Josas.

% init
Nn = size(nodes, 1);
labels = (1:Nn)';

% iteration
modif = true;
while modif
modif = false;

for i=1:Nn
neigh = getNeighbourNodes(i, edges);
neighLabels = labels([i;neigh]);

% check for a modification
if length(unique(neighLabels))>1
modif = true;
end

% put new labels
labels(ismember(labels, neighLabels)) = min(neighLabels);
end
end

% change to have fewer labels
labels2 = unique(labels);
for i=1:length(labels2)
labels(labels==labels2(i)) = i;
end

function nodes2 = getNeighbourNodes(node, edges)
%GETNEIGHBOURNODES find nodes adjacent to a given node
%
%   NEIGHS = getNeighbourNodes(NODE, EDGES)
%   NODE: index of the node
%   EDGES: the complete edges list
%   NEIGHS: the nodes adjacent to the given node.
%
%   NODE can also be a vector of node indices, in this case the result is
%   the set of neighbors of any input node.
%
%
%   -----
%
%   author : David Legland
%   INRA - TPV URPOI - BIA IMASTE
%   created the 16/08/2004.
%

%   HISTORY
%   10/02/2004 documentation
%   13/07/2004 faster algorithm
%   03/10/2007 can specify several input nodes

[i, j] = find(ismember(edges, node));
nodes2 = edges(i,1:2);
nodes2 = unique(nodes2(:));
nodes2 = sort(nodes2(~ismember(nodes2, node)));

function curves = graph2Contours(nodes, edges)
%GRAPH2CONTOURS convert a graph to a set of contour curves
%
%   CONTOURS = GRAPH2CONTOURS(NODES, EDGES)
%   NODES, EDGES is a graph representation (type "help graph" for details)
%   The algorithm assume every node has degree 2, and the set of edges
%   forms only closed loops. The result is a list of indices arrays, each
%   array containing consecutive point indices of a contour.
%
%   To transform contours into drawable curves, please use :
%   CURVES{i} = NODES(CONTOURS{i}, :);
%
%
%   NOTE : contours are not oriented. To manage contour orientation, edges
%   also need to be oriented. So we must precise generation of edges.
%
%   -----
%
%   author : David Legland
%   INRA - TPV URPOI - BIA IMASTE
%   created the 05/08/2004.
%

curves = {};
c = 0;

while size(edges,1)>0
% find first point of the curve
n0 = edges(1,1);
curve = n0;

% second point of the curve
n = edges(1,2);
e = 1;

while true
% add current point to the curve
curve = [curve n];

% remove current edge from the list
edges = edges((1:size(edges,1))~=e,:);

% find index of edge containing reference to current node
e = find(edges(:,1)==n | edges(:,2)==n);
e = e(1);

% get index of next current node
% (this is the other node of the current edge)
if edges(e,1)==n
n = edges(e,2);
else
n = edges(e,1);
end

% if node is same as start node, loop is closed, and we stop
% node iteration.
if n==n0
break;
end
end

% remove the last edge of the curve from edge list.
edges = edges((1:size(edges,1))~=e,:);

% add the current curve to the list, and start a new curve
c = c+1;
curves{c} = curve;
end
```