Average Curvature of a Closed 3D Surface

10 views (last 30 days)
Hi, All --
I would like to compute the average curvature of a closed 3D surface for which I know the inside-outside function, F(x,y,z)=0. (FWIW, centered on the origin and symmetric about all three Cartesian axes.)
I have implemented a couple of different approaches for Gaussian and mean curvature using divergence, the Hessian, the first and second fundamental forms, and other approaches of which I have a similarly weak grasp. I am generally able to get things to work for degenrate cases (e.g., a sphere), but if I increase the complexity of my object, I am tending to get answers that imply I am flirting with the precipice of numerical instability (e.g., imaginary results where Re(x)/Im(x)~10^8).
I like the idea of this approach because it "feels clean": (1) use continuous function; (2) apply magic; (3) integrate in spherical coordinates; and (4) profit. However, I am beginning to think that I need to tune out the sires sweetly singing and go for a more practical (i.e., tractable) approach.
My other idea is to start by using the inside-outside function to generate [X,Y,Z] similar to what is used by surf(). I can then use surfature() from the FEX to get the curvatures at every point on my [X,Y,Z] mesh. Finally, I could convert everything to spherical coordinates and integrate the curvature arrays to get average values.
My concern here is that reregularly-spaced [X,Y,Z] does not translate to regularly-spaced [r,theta,phi], so the calculation is somehow pathologically biased. I am also unsure about how to do the actual integration after converting my array to spherical coordinates.
Apologies for such a long text-dense question. I thought about trying to illustrate this better with a couple of code snippets, but the algebra is really messy and not all that informative.
Thanks, in advance.
  13 Comments
Paul
Paul on 15 Jan 2025
If you're only getting correct solutions sometimes ....
Here's an approach to computing the Gaussian curvature based on an equation in the link cited in the comment above.
Define a sphere
x = sym('x',[1 3]);
syms r real
F = sum(x.*x) - r^2
F = 
Compute the Gaussian curvature
gradF = gradient(F,x).';
hessF = hessian (F,x);
K_sphere = gradF*adjoint(hessF)*gradF.'/(gradF*gradF.')^2;
K_sphere(x) = simplify(subs(K_sphere,x(3)^2,rhs(isolate(F==0,x(3)^2))))
K_sphere(x1, x2, x3) = 
Define an ellipsoid and compute the Gaussian curvature
syms a b c positive
F = sum((x.^2./[a b c].^2)) - 1
F = 
gradF = gradient(F,x).';
hessF = hessian (F,x);
K_ellip = gradF*adjoint(hessF)*gradF.'/(gradF*gradF.')^2;
K_ellip(x) = simplify(subs(K_ellip,x(3)^2,rhs(isolate(F==0,x(3)^2))))
K_ellip(x1, x2, x3) = 
The result matches eqn (16) at Ellipsoid
Both results above took an extra step to get K as a function of two or fewer variables.
Try with the equation above, ignoring absolute values as directed ...
syms epsilon_1 epsilon_2 positive
r = sym('r',[1 3],'positive');
F = sum((x(1:2)./r(1:2)).^(2/epsilon_2))^(epsilon_2/epsilon_1) + (x(3)/r(3))^(2/epsilon_1) - 1
F = 
gradF = gradient(F,x).';
hessF = hessian (F,x);
K_moe = gradF*adjoint(hessF)*gradF.'/(gradF*gradF.')^2;
K_moe = simplify(K_moe)
K_moe = 
If neccesary to eliminate one of the variables, the best I could do for now is (ignoring analytic constraints always is a bit uncomfortable because I don't know why that directive is needed)
isolate(F==0,(x(3)/r(3))^(2/epsilon_1))
ans = 
sol = solve(ans,x(3),'IgnoreAnalyticConstraints',true,'ReturnConditions',true)
sol = struct with fields:
x3: r3*(1 - ((x1/r1)^(2/epsilon_2) + (x2/r2)^(2/epsilon_2))^(epsilon_2/epsilon_1))^(epsilon_1/2) parameters: [1x0 sym] conditions: symtrue
sol.x3
ans = 
K_moe(x) = subs(K_moe,x(3),sol.x3)
K_moe(x1, x2, x3) = 
That's not a very nice expression, but it should be correct unless the original equation for the curvature doesn't apply to this particular surface or if that solve for x(3) isn't true for some reason.
Here's one sanity check that seems correct
subs(K_moe,[epsilon_1,epsilon_2],[2,2])
ans(x1, x2, x3) = 
0
At this point, I'm not sure what you'd want to do with any of the K expressions. I gather you want to compute some sort of "average" curvature as a figure of merit. How would that be defined? As a surface integral of K over the surface normalized by something?
Moe Szyslak
Moe Szyslak on 16 Jan 2025
Hey, Paul -- this is fantastic -- thanks so much. This is exactly what I was trying to do in developing a closed-form solution to the problem.
I am not very familiar with MATLAB's symbolic math functionality. and I think where I may have been going wrong was in attempting to develop the analytical solution independently (using Mathcad, gasp!) prior to implementing in MATLAB. Your approach is much cleaner and more robust. I am going to play around with this some and see if I can either break it or convince myself that it does what I think it does.
Ultimately, yes, I am looking to get the average value of curvature over the entire surface. My instinct is to apply a spherical transformation and then integrate over the range (exploiting symmetry). My concern with this, however, is that all of the "action" is happening in areas that are near one of the Cartesian coordinate planes -- that is, at the bounds of my integration. I am wondering if it makes more sense to take the average of the absolute value of the curvature over the entire surface () rather than just 1/8th of it.
Thanks again for all of your help. I am learning a lot.

Sign in to comment.

Accepted Answer

Mathieu NOE
Mathieu NOE on 10 Jan 2025
Edited: Mathieu NOE on 10 Jan 2025
that's me again ! :)
so a bit for my own fun , I tried a few things
here I decided I wanted to define the intersection of your surface with a 3D rectangular box (any other shape can be done , and maybe here the spherical coordinates can be useful to create other pavement shapes)
from the "red dots" carpet you can compute the integral (or mean) of K (or H) over this area with the triangulation method
you can reduce the constrain of using a very refined mesh as the triangulation method is pretty accurate even with fairly coarse resolution.
if you compare the result of the triangulation mean with the simple average values of selected K values (w/o using the uneven areas info) you see both methods will not give you the same results (that's obvious , sorry ! ) , unless you use a very refined mesh resolution, but that is not my recommendation.
in other words , keep the resolution at reasonnable value (N = 100 seems to suffice in my eyes) and use the triangulation integration method (the rest is just fyi)
hope it helps !
% Driver script to create a superquadric, calculate the surfavece area and
% volume, generate a surface mesh in Cartesian coordinates, compute the
% curvature, and convert the surface mesh to spherical coordinates.
% Inside-outside function for superquadric
% f(x,r,eps) =
% ((x/rx)^(2/eps2)+(y/ry)^(2/eps2))^(eps2/eps1)+(z/rz)^(2/eps1)
% Preliminaries
close all;
clear;
% Input Values
rx = 0.6; ry = 1.2; rz = 1.0; % semiaxis lengths
eps1 = 1.4; eps2 = 0.6; % shape exponents
n = 100; % resolution for discretization
% Compute volume and surface area
V = 2*rx.*ry.*rz.*eps1.*eps2.*beta(eps1/2,eps1+1).*beta(eps2/2,(eps2+2)/2);
SA = superellipsoid_SA2(rx,ry,rz,eps1,eps2);
% Discretize and compute curvature
[X,Y,Z] = superquad(rx,ry,rz,eps1,eps2,n);
[K,H,P1,P2] = surfature(X,Y,Z);
% Plot object and colormap of curvature
surfl(X,Y,Z), shading interp, colormap(copper), axis equal
figure;
% surf(X,Y,Z,K,'facecolor','interp','edgecolor','none');
surf(X,Y,Z,K);
set(gca,'clim',[-1,1]);
colorbar;
axis equal;
% % Convert to spherical coordinates
% [az,el,rho] = cart2sph(X,Y,Z);
% extract a local area (intersection of you surface with a rectangular box)
indX = X>-0.5 & X < -0.3;
indY = Y>-0.75 & Y < 0;
indZ = Z> 0.2 & Z < 0.5;
ind = indX & indY & indZ;
xlocal = X(ind);
ylocal = Y(ind);
zlocal = Z(ind);
hold on
scatter3(xlocal,ylocal,zlocal,25,"r","filled");
%% 2D integration using triangulation:
% Let's say your integration area is given by the triangulation "trep",
% which for example could be obtained by trep = delaunayTriangulation(x(:), y(:)).
% If you have your values z corresponding to z(i) = f(trep.Points(i,1), trep.Points(i,2)),
% you can use the following integration routine.
% It computes the exact integral of the linear interpolant.
% This is done by evaluating the areas of all the triangles and then using these areas
% as weights for the midpoint(mean)-value on each triangle.
trep = delaunayTriangulation(xlocal(:), ylocal(:));
%% THIS code below can be deleted latter on - just fyi so you don't have to ask you the same question as I did
% xtrep = trep.Points(:,1);
% ytrep = trep.Points(:,2);
% % NB xtrep = xlocal and same for ytrep = ylocal. No need to create interpolated z values here
% % proof in the plot below
% figure,
% plot(xlocal,ylocal,'*r');
% hold on
% plot(xtrep,ytrep,'ob');
% let's do the integration now
[intZ,meanZ] = integrateTriangulation(trep, zlocal)
% now compare meanZ (true mean) with mean(zlocal) (is of course a crude approx as we don't take
% into account the non uniform grid into consideration)
zlocal_mean = mean(zlocal)
% by refining the mesh , the two values will converge .
% The triangulation method allows a better and faster approach
% as you can work with a coarser mesh without loss of accuracy
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [intZ,meanZ] = integrateTriangulation(trep, z)
P = trep.Points; T = trep.ConnectivityList;
d21 = P(T(:,2),:)-P(T(:,1),:);
d31 = P(T(:,3),:)-P(T(:,1),:);
areas = abs(1/2*(d21(:,1).*d31(:,2)-d21(:,2).*d31(:,1)));
% integral of Z
intZ = areas'*mean(z(T),2);
% mean of Z = intZ divided by total area
meanZ = intZ/(trapz(areas));
end
  3 Comments
Moe Szyslak
Moe Szyslak on 13 Jan 2025
OK, thanks to your help, Mathieu, I think that the script below does mostly what I want.
My concern is that I observe asymptotic behavior only for very high resolutions. For reference, my laptop (16 GB RAM) protests n=10000 and does n=5000 in about 4 min. My workstation (64 GB RAM) takes about 5 min for n=10000 and 15 min for n = 15000, but throws an error for n=20000. So, we're getting to the outer limits of what is reasonable, given that I have many thousands of surfaces to characterize.
n meanK
100 1.203
200 1.295
500 1.423
1000 1.528
2000 1.642
5000 1.817
10000 1.974
15000 2.076
% Driver script to create a superquadric, calculate the surfavece area and
% volume, generate a surface mesh in Cartesian coordinates, compute the
% curvature, and convert the surface mesh to spherical coordinates.
%
% Acknowledgements:
% Significant assistance from Mathieu Noe of the MATLAB community.
% www.mathworks.com/matlabcentral/answers/2172786-average-curvature-of-a-closed-3d-surface
% Inside-outside function for superquadric
% f(x,r,eps) =
% ((x/rx)^(2/eps2)+(y/ry)^(2/eps2))^(eps2/eps1)+(z/rz)^(2/eps1)
% Preliminaries
close all;
clear;
% Input Values
rx = 1.25; ry = 0.75; rz = 1.0; % semiaxis lengths
eps1 = 1.5; eps2 = 0.5; % shape exponents
n = 1000; % resolution for discretization
% Compute volume and surface area
V = 2*rx.*ry.*rz.*eps1.*eps2.*beta(eps1/2,eps1+1).*beta(eps2/2,(eps2+2)/2);
SA = superellipsoid_SA2(rx,ry,rz,eps1,eps2);
% Discretize and compute curvature
[X,Y,Z] = superquad(rx,ry,rz,eps1,eps2,n);
[K,H,P1,P2] = surfature(X,Y,Z);
% Plot object and colormap of curvature
%surfl(X,Y,Z), shading interp, colormap(copper), axis equal
figure;
surf(X,Y,Z,K,'facecolor','interp','edgecolor','none');
set(gca,'clim',[-5,5]);
colorbar;
axis equal;
% extract a local area (top half of the surface)
indX = X >= min(min(X)) & X <= max(max(X));
indY = Y >= min(min(Y)) & Y <= max(max(Y));
indZ = Z >= 0 & Z <= max(max(Z));
indK = ~isnan(K);
ind = indX & indY & indZ & indK;
A = unique([X(ind) Y(ind) Z(ind) K(ind)],'rows','stable');
xlocal = A(:,1);
ylocal = A(:,2);
zlocal = A(:,3);
Klocal = A(:,4);
hold on
scatter3(xlocal,ylocal,zlocal,25,"r","filled");
% 2D integration using triangulation:
trep = delaunayTriangulation(xlocal(:), ylocal(:));
[intK,meanK,sArea] = integrateTriangulation(trep, zlocal, Klocal);
% COmpute naive average curvature
Klocal_mean = mean(Klocal);
% Output results
fprintf('Surface area (closed form):\t\t%f\n', SA);
fprintf('Surface area (triangulation):\t%f\n', 2*sArea);
fprintf('Area-weighted curvature:\t\t%f\n', meanK);
fprintf('Naive average curvature:\t\t%f\n', Klocal_mean);
fprintf('Particle volume:\t\t\t\t%f\n', V);
%% Support Functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [intK,meanK,sArea] = integrateTriangulation(trep, z, K)
P = trep.Points; T = trep.ConnectivityList;
V = [P z];
d21 = V(T(:,2),:)-V(T(:,1),:);
d31 = V(T(:,3),:)-V(T(:,1),:);
areas = vecnorm(cross(d21,d31)')/2;
% areas = abs(1/2*(d21(:,1).*d31(:,2)-d21(:,2).*d31(:,1)));
% integral of Z
intK = areas*mean(K(T),2);
% mean of Z = intZ divided by total area
sArea = sum(areas);
meanK = intK/sArea;
end
%eof
Anybody have any thoughts about how to get a "true" solution without so much resolution dependency?
Mathieu NOE
Mathieu NOE on 13 Jan 2025
Edited: Mathieu NOE on 13 Jan 2025
hello again (and tx for accepting my suggestions ! ) :)
I am not sure that is needed indeed :
indK = ~isnan(K);
ind = indX & indY & indZ & indK;
A = unique([X(ind) Y(ind) Z(ind) K(ind)],'rows','stable');
xlocal = A(:,1);
ylocal = A(:,2);
zlocal = A(:,3);
Klocal = A(:,4);
but what I see as possible issue in the existing code is that surfature works for regularly gridded surfaces with is not the case of your superquadric (see the way it is parametrized inside the superquad function)
so I made a test between the regular code (SQ_curvDriver_loop.m) and a modified one with linear interpolation using scatteredInterpolant (SQ_curvDriver_loop1.m)
using the same boundary conditions as you , but slightly corrected (in "modern" style) :
indX = X >= min(X,[],'all') & X <= max(X,[],'all');
indY = Y >= min(Y,[],'all') & Y <= max(Y,[],'all');
indZ = Z >= 0 & Z <= max(Z,[],'all');
the regular code (without any other modification than running a for loop for n = 100 300 700 1500 3100) will generate this result :
the modified code with scatteredInterpolant will generate this result :
seems that now we have a feeling that both methods converge , and to the same limit !!
what would be great is now to have the "true" analytical result to compare with...
now if you want to avoid the additionnal burden of scatteredInterpolant , you need to find a way to change the way the surface is parametrized and use instead linear gridded x, y data.
both codes are in attachment
hope it helps !

Sign in to comment.

More Answers (0)

Categories

Find more on Computational Geometry in Help Center and File Exchange

Products


Release

R2024b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!