Home > matGeom > meshes3d > meshCurvatures.m

meshCurvatures

PURPOSE ^

MESHCURVATURES Compute principal curvatures on mesh vertices.

SYNOPSIS ^

function [C1, C2, U1, U2, H, K, N] = meshCurvatures(vertices, faces, varargin)

DESCRIPTION ^

MESHCURVATURES Compute principal curvatures on mesh vertices.

   [C1, C2] = meshCurvatures(VERTICES, FACES)
   Computes the principal curvatures C1 and C2 for each vertex of the mesh
   defined by VERTICES and FACES.

   [C1, C2] = meshCurvatures(..., PNAME, PVALUE)
   Provides additional input arguments based on a list of name-value pairs
   of arguments. Parameter names can be:
   * 'SmoothingSteps'      (integer, default: 3) 
       Specifies the number of steps for smoothing vertex curvature
       tensors.  
   * 'Verbose'             (boolean, default: true) 
       Displays details about algorithm processing. 
   * 'ShowProgress'        (boolean, default: true) 
       Displays a text-based progress bar.

   Algorithm
   The function is adapted from the "compute_curvature" function, in the
   "toolbox_graph" from Gabriel Peyre.
   The basic idea is to define a curvature tensor for each edge, by
   assigning a minimum curvature equal to zero along the edge, and a
   maximum curvature equal to the dihedral angle across the edge.
   Averaging around the neighbors of a vertex v yields a summation formula
   over the neighbor edges to compute the curvature tensor of a vertex:
           1
   C(v) = ----     Sum       \beta(e) || e \cap A(v) || ebar ebar^t
          A(v)  {e \in A(v)}
   where:
   * A(v) is the neighborhood region, usually defined as a 'ring' around
       the vertex v
   * beta(e) is the dihedral angle between the normals of the two faces
       incident to edge e
   * || e \cap A(v) || is the length of e (more exactly, the length of the
       part of e contained within the neighborhood region
   * ebar is the normalized edge

   The curvature tensor is then decomposed into C = P D P^-1, with P
   containing main direction vectors and normal, and D being a diagonal
   matrix with the two main curvatures and zero along the diagonal.
   
   References
   * David Cohen-Steiner and Jean-Marie Morvan (2003). 
       "Restricted Delaunay triangulations and normal cycle". 
       In Proc. 19th Annual ACM Symposium on Computational Geometry, 
       pages 237-246. 
   * Pierre Alliez, David Cohen-Steiner, Olivier Devillers, Bruno Levy,
       and Mathieu Desbrun (2003). "Anisotropic Polygonal Remeshing". 
       ACM Transactions on Graphics. 
       (SIGGRAPH '2003 Conference Proceedings)
   * Mario Botsch, Leif Kobbelt, M. Pauly, P. Alliez, B. Levy (2010).
       "Polygon Mesh Processing", Taylor and Francis Group, New York.
   
   Example
     [v, f] = torusMesh;
     f2 = triangulateFaces(f);
     [c1, c2] = meshCurvatures(v, f2);
     figure; hold on; axis equal; view(3);
     drawMesh(v, f2, 'VertexColor', c1 .* c2);

   See also
     meshes3d, drawMesh, triangulateFaces

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SOURCE CODE ^

0001 function [C1, C2, U1, U2, H, K, N] = meshCurvatures(vertices, faces, varargin)
0002 %MESHCURVATURES Compute principal curvatures on mesh vertices.
0003 %
0004 %   [C1, C2] = meshCurvatures(VERTICES, FACES)
0005 %   Computes the principal curvatures C1 and C2 for each vertex of the mesh
0006 %   defined by VERTICES and FACES.
0007 %
0008 %   [C1, C2] = meshCurvatures(..., PNAME, PVALUE)
0009 %   Provides additional input arguments based on a list of name-value pairs
0010 %   of arguments. Parameter names can be:
0011 %   * 'SmoothingSteps'      (integer, default: 3)
0012 %       Specifies the number of steps for smoothing vertex curvature
0013 %       tensors.
0014 %   * 'Verbose'             (boolean, default: true)
0015 %       Displays details about algorithm processing.
0016 %   * 'ShowProgress'        (boolean, default: true)
0017 %       Displays a text-based progress bar.
0018 %
0019 %   Algorithm
0020 %   The function is adapted from the "compute_curvature" function, in the
0021 %   "toolbox_graph" from Gabriel Peyre.
0022 %   The basic idea is to define a curvature tensor for each edge, by
0023 %   assigning a minimum curvature equal to zero along the edge, and a
0024 %   maximum curvature equal to the dihedral angle across the edge.
0025 %   Averaging around the neighbors of a vertex v yields a summation formula
0026 %   over the neighbor edges to compute the curvature tensor of a vertex:
0027 %           1
0028 %   C(v) = ----     Sum       \beta(e) || e \cap A(v) || ebar ebar^t
0029 %          A(v)  {e \in A(v)}
0030 %   where:
0031 %   * A(v) is the neighborhood region, usually defined as a 'ring' around
0032 %       the vertex v
0033 %   * beta(e) is the dihedral angle between the normals of the two faces
0034 %       incident to edge e
0035 %   * || e \cap A(v) || is the length of e (more exactly, the length of the
0036 %       part of e contained within the neighborhood region
0037 %   * ebar is the normalized edge
0038 %
0039 %   The curvature tensor is then decomposed into C = P D P^-1, with P
0040 %   containing main direction vectors and normal, and D being a diagonal
0041 %   matrix with the two main curvatures and zero along the diagonal.
0042 %
0043 %   References
0044 %   * David Cohen-Steiner and Jean-Marie Morvan (2003).
0045 %       "Restricted Delaunay triangulations and normal cycle".
0046 %       In Proc. 19th Annual ACM Symposium on Computational Geometry,
0047 %       pages 237-246.
0048 %   * Pierre Alliez, David Cohen-Steiner, Olivier Devillers, Bruno Levy,
0049 %       and Mathieu Desbrun (2003). "Anisotropic Polygonal Remeshing".
0050 %       ACM Transactions on Graphics.
0051 %       (SIGGRAPH '2003 Conference Proceedings)
0052 %   * Mario Botsch, Leif Kobbelt, M. Pauly, P. Alliez, B. Levy (2010).
0053 %       "Polygon Mesh Processing", Taylor and Francis Group, New York.
0054 %
0055 %   Example
0056 %     [v, f] = torusMesh;
0057 %     f2 = triangulateFaces(f);
0058 %     [c1, c2] = meshCurvatures(v, f2);
0059 %     figure; hold on; axis equal; view(3);
0060 %     drawMesh(v, f2, 'VertexColor', c1 .* c2);
0061 %
0062 %   See also
0063 %     meshes3d, drawMesh, triangulateFaces
0064 %
0065 
0066 % ------
0067 % Author: David Legland
0068 % E-mail: david.legland@inrae.fr
0069 % Created: 2021-09-21, using Matlab 9.10.0.1684407 (R2021a) Update 3
0070 % Copyright 2021-2024 INRAE - BIA Research Unit - BIBS Platform (Nantes)
0071 
0072 %% Process input arguments
0073 
0074 % default values for options
0075 nIters = 3;
0076 verbose = true;
0077 showProgress = true;
0078 
0079 while length(varargin) > 1
0080     name = varargin{1};
0081     if strcmpi(name, 'SmoothingSteps')
0082         nIters = varargin{2};
0083     elseif strcmpi(name, 'Verbose')
0084         verbose = varargin{2};
0085     elseif strcmpi(name, 'ShowProgress')
0086         showProgress = varargin{2};
0087     else
0088         error('Unknown option: %s', name);
0089     end
0090     varargin(1:2) = [];
0091 end
0092 
0093 % validate vertices
0094 if ~isnumeric(vertices) || size(vertices, 2) ~= 3
0095     error('Requires vertices to be a N-by-3 numeric array');
0096 end
0097 
0098 % ensure faces are triangular
0099 if ~isnumeric(faces) || size(faces, 2) > 3
0100     warning('requires triangle mesh, forces triangulation');
0101     faces = triangulateFaces(faces);
0102 end
0103 
0104 
0105 %% Retrieve adjacency relationships
0106 
0107 if verbose
0108     disp('compute adjacencies');
0109 end
0110 
0111 % number of elements of each type
0112 nv = size(vertices, 1);
0113 nf = size(faces, 1);
0114 
0115 % ev1 and ev2 are indices of source and target vertex of each edge
0116 % (recomputed later)
0117 ev1 = [faces(:,1); faces(:,2); faces(:,3)];
0118 ev2 = [faces(:,2); faces(:,3); faces(:,1)];
0119 
0120 % Compute sparse matrix representing edge-to-face adjacency
0121 s = [1:nf 1:nf 1:nf]';
0122 A = sparse(ev1, ev2, s, nv, nv); 
0123 
0124 % converts sparse matrix to indices of adjacent vertices and faces
0125 [~, ~, ef1] = find(A);         % index of 'right' face
0126 [ev1, ev2, ef2] = find(A');    % index of 'left' face, and of vertices
0127 
0128 % edges are consdered twice (one for each vertex)
0129 % keep only the edge with lower source index
0130 inds = find(ev1 < ev2);
0131 ef1 = ef1(inds);
0132 ef2 = ef2(inds);
0133 ev1 = ev1(inds); 
0134 ev2 = ev2(inds);
0135 
0136 % number of edges
0137 ne = length(ev1);
0138 
0139 
0140 %% Compute geometry features
0141 
0142 % compute edge direction vectors
0143 edgeVectors = vertices(ev2,:) - vertices(ev1,:);
0144 
0145 % normalize edge direction vecotrs
0146 d = sqrt(sum(edgeVectors.^2, 2));
0147 edgeVectors = bsxfun(@rdivide, edgeVectors, d);
0148 
0149 % avoid too large numerics
0150 d = d ./ mean(d);
0151 
0152 % normals to faces
0153 normals = meshFaceNormals(vertices, faces);
0154 
0155 % ensure normals point outward the mesh
0156 if meshVolume(vertices, faces) < 0
0157     normals = -normals;
0158 end
0159 
0160 % inner product of normals
0161 dp = sum(normals(ef1, :) .* normals(ef2, :), 2);
0162 
0163 % compute the (unsigned) dihedral angle between the normals of the two
0164 % faces incident to each edge
0165 beta = acos(min(max(dp, -1), 1));
0166 
0167 % relatice orientation of face normals cross product and edge orientation
0168 cp = crossProduct3d(normals(ef1, :), normals(ef2, :));
0169 si = sign(sum(cp .* edgeVectors, 2));
0170 
0171 % compute signed dihedral angle
0172 beta = beta .* si;
0173 
0174 
0175 %% Compute tensors
0176 
0177 if verbose
0178     disp('compute edge tensors');
0179 end
0180 
0181 % curvature tensor of each edge
0182 T = zeros(3, 3, ne);
0183 for i = 1:3
0184     for j = 1:i
0185         T(i, j, :) = reshape(edgeVectors(:,i) .* edgeVectors(:,j), 1, 1, ne);
0186         T(j, i, :) = T(i, j, :);
0187     end
0188 end
0189 T = bsxfun(@times, T, reshape(d .* beta, [1 1 ne]));
0190 
0191 % curvature tensor of each vertex by pooling edge tensors
0192 Tv = zeros(3, 3, nv);
0193 w = zeros(1, 1, nv);
0194 for k = 1:ne
0195     if showProgress
0196         progressbar(k, ne);
0197     end
0198     Tv(:,:,ev1(k)) = Tv(:,:,ev1(k)) + T(:,:,k);
0199     Tv(:,:,ev2(k)) = Tv(:,:,ev2(k)) + T(:,:,k);
0200     w(:,:,ev1(k)) = w(:,:,ev1(k)) + 1;
0201     w(:,:,ev2(k)) = w(:,:,ev2(k)) + 1;
0202 end
0203 w(w < eps) = 1;
0204 Tv = Tv ./ repmat(w, [3 3 1]);
0205 
0206 if verbose
0207     disp('average vertex tensors');
0208 end
0209 
0210 % apply smoothing on the tensor field
0211 for i = 1:3
0212     for j = 1:3
0213         a = Tv(i, j, :);
0214         a = smoothMeshFunction(vertices, faces, a(:), nIters);
0215         Tv(i, j, :) = reshape(a, [1 1 nv]);
0216     end
0217 end
0218 
0219 
0220 %% Retrieve curvatures and eigen vectors from tensors
0221 
0222 if verbose
0223     disp('retrieve curvatures');
0224 end
0225 
0226 % allocate memory
0227 U = zeros(3, 3, nv);
0228 D = zeros(3, nv);
0229 
0230 % iterate over vertices
0231 for k = 1:nv
0232     % display progress
0233     if showProgress
0234         progressbar(k,nv);
0235     end
0236     
0237     % extract eigenvectors and eigenvalues for current vertex
0238     [u, d] = eig(Tv(:,:,k));
0239     d = real(diag(d));
0240     
0241     % sort acording to [normal, min curv, max curv]
0242     [~, I] = sort(abs(d));    
0243     D(:, k) = d(I);
0244     U(:, :, k) = real(u(:,I));
0245 end
0246 
0247 % retrieve main curvatures and associated directions
0248 C1 = D(2,:)';
0249 C2 = D(3,:)';
0250 U1 = squeeze(U(:,3,:))';
0251 U2 = squeeze(U(:,2,:))';
0252 
0253 % enforce C1 < C2
0254 inds = find(C1 > C2);
0255 C1tmp = C1; 
0256 U1tmp = U1;
0257 C1(inds) = C2(inds); 
0258 C2(inds) = C1tmp(inds);
0259 U1(inds,:) = U2(inds,:); 
0260 U2(inds,:) = U1tmp(inds,:);
0261 
0262 % compute optional output arguments
0263 if nargout > 4
0264     % average and gaussian curvatures
0265     H = (C1 + C2) / 2;
0266     K = C1 .* C2;
0267     
0268     if nargout > 6
0269         % normal vector for each vertex
0270         N = squeeze(U(:,1,:))';
0271     end
0272 end

Generated on Thu 21-Nov-2024 11:30:22 by m2html © 2003-2022