File Exchange

image thumbnail

unifyMeshNormals

version 1.2.0.0 (4.63 KB) by Sven
Aligns adjacent faces in a triangulated mesh surface or volume

12 Downloads

Updated 11 Sep 2013

View License

UNIFYMESHNORMALS Aligns mesh normals to all point in a consistent direction.

F_OUT = UNIFYMESHNORMALS(F,V) takes an N-by-3 array of faces F, and
returns an equivalent set of faces F_OUT with all adjacent faces in F_OUT
pointing in a consistent direction. Vertices V are also required in "in"
or "out" face alignment is specified (see below).

FV_OUT = UNIFYMESHNORMALS(FV) instead take a structure array with field
"faces" (and "vertices"), returning that structure with adjacent faces
aligned consistently as above.

[F_OUT, FLIPPED] = UNIFYMESHNORMALS(...) also returns FLIPPED, an N-by-1
logical mask showing which faces in F/FV were flipped during unification.

[...] = UNIFYMESHNORMALS(...,'alignTo','in')
[...] = UNIFYMESHNORMALS(...,'alignTo','out')
[...] = UNIFYMESHNORMALS(...,'alignTo',FACE) allows the user to specify a
single trusted FACE number which will remain unflipped, and all other
faces will be aligned to it. FACE may also be the string 'in' or 'out'.
'in' will result in all faces aligned, with direction towards the center
of the object. 'out' will result in directions pointing outwards.

Example:
tmpvol = zeros(20,20,20); % Empty voxel volume
tmpvol(5:15,8:12,8:12) = 1; % Turn some voxels on
tmpvol(8:12,5:15,8:12) = 1;
tmpvol(8:12,8:12,5:15) = 1;
fv = isosurface(tmpvol, 0.99); % Create the patch object
% Display patch object normal directions
facets = fv.vertices';
facets = permute(reshape(facets(:,fv.faces'), 3, 3, []),[2 1 3]);
edgeVecs = facets([2 3 1],:,:) - facets(:,:,:);
allFacNorms = bsxfun(@times, edgeVecs(1,[2 3 1],:), edgeVecs(2,[3 1 2],:)) - ...
bsxfun(@times, edgeVecs(2,[2 3 1],:), edgeVecs(1,[3 1 2],:));
allFacNorms = bsxfun(@rdivide, allFacNorms, sqrt(sum(allFacNorms.^2,2)));
facNorms = num2cell(squeeze(allFacNorms)',1);
facCents = num2cell(squeeze(mean(facets,1))',1);
facEdgeSize = mean(reshape(sqrt(sum(edgeVecs.^2,2)),[],1,1));
figure
patch(fv,'FaceColor','g','FaceAlpha',0.2), hold on, quiver3(facCents{:},facNorms{:},facEdgeSize), view(3), axis image
title('All normals point IN')
% Turn over some random faces to point the wrong way
flippedFaces = rand(size(fv.faces,1),1)>0.75;
fv_turned = fv;
fv_turned.faces(flippedFaces,:) = fv_turned.faces(flippedFaces,[2 1 3]);
figure, patch(fv_turned,'FaceColor','flat','FaceVertexCData',double(flippedFaces))
colormap(summer), caxis([0 1]), view(3), axis image
% Fix them to all point the same way
[fv_fixed, fixedFaces] = unifyMeshNormals(fv_turned);
figure, patch(fv_fixed,'FaceColor','flat','FaceVertexCData',double(xor(flippedFaces,fixedFaces)))
colormap(summer), caxis([0 1]), view(3), axis image

See also SPLITFV, INPOLYHEDRON, STLWRITE

Comments and Ratings (3)

chacoon

If you exchange (line 178ff)

totalvol = sum(tetvols);

% If the volume is negative, it means we have faces pointed IN and vice
% versa. If they asked for the opposite, we need to flip one more time.
if totalvol>0 && strcmpi(options.alignTo,'in') || ...
totalvol<0 && strcmpi(options.alignTo,'out')
f = f(:,[2 1 3]);
facesFlipped = ~facesFlipped;
end

by

for i = 1:numel(setsToFlip)
totalvol(i) = sum(tetvols(faceSets==i));

% If the volume is negative, it means we have faces pointed IN and vice
% versa. If they asked for the opposite, we need to flip one more time.
if totalvol(i)>0 && strcmpi(options.alignTo,'in') || ...
totalvol(i)<0 && strcmpi(options.alignTo,'out')
f(faceSets==i,:) = f(faceSets==i,[2 1 3]);
facesFlipped(faceSets==i) = ~facesFlipped(faceSets==i);
end
end

it also works reliably with multiple surface sets!

Well done!

Ran out of memory. How about a loop instead of recursion for large matrices?

Scott

Updates

1.2.0.0

Added more robust 'in' vs 'out' alignment via calculated mesh volume.

1.1.0.0

Added ability to align faces to point 'in' or 'out' from an object

MATLAB Release Compatibility
Created with R2013a
Compatible with any release
Platform Compatibility
Windows macOS Linux