The above picture shows examples of K-surfaces, i.e. surfaces with constant Gaussian curvature
Discrete K-surfaces
Tutorial 12: The Pendulum
A pendulum is a body suspended from a fixed support so that it swings freely back and forth under the influence of gravity. A simple pendulum is an idealization of a real pendulum—a point of mass
Its position is described by an angular coordinate
In the following we will set
The simple pendulum is what is called an integrable Hamiltonian system. Now, if we discretize
Here
The phase space is not ordered anymore by the constants of motion. Though order is not totally gone. The chaotic regions cannot diffuse over the whole phase space but are bounded by so-called KAM orbits. As guaranteed by Kolmogorov–Arnold–Moser (KAM) theory, a non-neglectable amount will survive for small perturbations.
Remarkably, if we use
Here again
To produce these pictures in Houdini one can just randomly seed a bunch of particles on a the square
Here is a way to do this: In the
The copy and transform nodes at the end are used to copy the fundamental domain in
Homework (due 9 July). Build a network that visualizes the orbits of the pendulum equation. It shall be possible to specify
Tutorial 11: CMC Snails, Tires and Soap Bubbles
In previous tutorials we compute immersed minimal surfaces. These immersions were the critical points of Dirichlet energy. You have seen in the lecture that, if we additionally include a volume constraint, then the critical points have no longer mean curvature
Let
If we look at the critical points of
So if we minimize for a given fixed
The equation above also has a nice Newtonian interpretation: By Newtons second law the above equation describes a surface on which act two forces—surface tension
For the second derivative we use the same ansatz as last time and the first derivative is approximated by a difference quotient: For given stepsize
One might be tempted to approximate the first derivative by a more symmetric expression like the average of difference quotients, but this is doomed. As Albert explained to me, that’s well known—e.g. if one discretizes the left hand side of the diffusion equation
Let
As a short video shows, the method converges quickly.
Here the corresponding network—it just prepares the initial conditions and implements the flow using a solver node.
More interestingly, this also works for more generic boundary curves producing CMC surface which are no longer a part of a sphere.
In principle triangles can degenerate during the flow. To prevent them from becoming too bad we just used a polydoctor node (inside the solver) to repair the mesh if it becomes necessary. Not all situations can be fixed this way but it often help a lot.
For small pressure
From physics we know that pressure is not constant—the ideal gas law says that, up to physical constants,
Let us come back to the problem of computing CMC surfaces. Therefore let us now impose a hard volume constraint, i.e. we minimize Dirichlet energy over all immersions which satisfy
- for fixed
, minimize the unconstraint problem - use the minimizer
to update
If we don’t minimize to the very end in the first step, but instead change it to a finite amount of gradient descent, and interpret the update of
Now let us look at a stationary point
Let us again change the first line of the system above to second order with damping. The final system then becomes
So, for a given stepsize
This all translates nicely to the discrete case–the Laplace operator is given by cotangent weights and the gradient the volume is the vertex normal field
Here a movie of the dynamics of a flat ellipsoid blowing up to the CMC surface in the teaser image. The method is not restricted to disks. Below you find an image of a CMC cylinder.
Homework (due 2 July). Build a network which flows a given surface to a CMC surface of prescribed volume.
Symplectic Maps and Flows
Consider a physical system consisting of
where
The evolution of the system is described by a smooth map
Suppose that the only forces acting on the particles come from a fixed potential energy function
For the state vector
We view the right-hand side as a vector field
This particular vector field has striking special properties, but before discussing these let us switch to a more general context: Suppose
has a solution
by the property that
and for all
Now we are ready to give the definitions needed for understanding what is special about the vector field
Let
Definition 1: The skew-symmetric bilinear form
is called the standard symplectic form on
Definition 2: A matrix
or equivalently, if
Definition 3: A matrix
or equivalently, if
The symplectic
Theorem 1:
(a) Let
(b) Let
Proof: If
This proves (a). For (b) let
Equipped with the necessary Linear Algebra we now can move on to Differential Geometry. Let
Definition 2: A diffeomorphism map
Definition 3: A vector field
Theorem 2:
(a) Let
is infinitesimally symplectic.
(b) Let
Proof: Let
and
Then for all
Our present claim (a) now follows from part (a) of Theorem 1. For (b) note that the existence and uniqueness of a smooth family of diffeomorphisms
Let us now come back to Newton’s equations of motion from the beginning of this post.
Theorem 3: Let
is infinitesimally symplectic.
Proof: We have
Since both
Tutorial 10: Wave Equation and Verlet Method
Let
For a discrete surface
To obtain the Verlet scheme one just approximates the second derivative of the function
Note that in the discrete setting the space of piecewise linear functions is finite dimensional and, if
We can easily build up a network to solve the wave equation on closed surfaces. Let us start here with a sphere on which we paint a function stored as a point attribute u
, which plays the role of u
to a second attribute uprev
playing the role of v@P += f@u*v@N
). Here the complete network:
Below you find an example how the initial
A movie of how the evolution looks like you can find here. Note that the bumps really almost reappear.
For surfaces with boundary we need to specify what happens at the boundary. One way to deal with boundary is to glue a reflected copy of the surface along the boundary—so one gets a closed surface without boundary but with a reflectional symmetry—and restrict the attention only to functions invariant under the reflection. Practically this means to do nothing special: For boundary edges there is only one cotangent so we have double it. Each edge which is not a boundary edge but ends at a boundary vertex appears with the same weights on the copy. So these appear twice in the sum as well. But also the area is doubled. So if we divide by area nothing changes. As a result the waves are reflected at the boundary, as shown in the picture below.
Here the corresponding video.
Homework (due 25 June). Build a network that simulates waves on surfaces.
Tutorial 9: Minimal Surfaces and Plateau Problem in
We are back at minimal surfaces in
Let
Though, if we restrict attention to maps into the
For discrete functions into
By the considerations above one easily finds that this definition actually amounts to defining the Dirichlet energy of a discrete function into the
Note that the Dirichlet problem—given a map
Let
If
Similarly to the Dirichlet energy we can define the area of a discrete map
The algorithm to solve the Dirichlet problem can be easily modified to solve the Plateau problem—just update edge lengths in each step.
To produce Lawson’s minimal surfaces we need to solve then the Plateau problem for geodesic quadrilaterals
Under the stereographic projection the quadrilateral
For the figure used the stereographic projection from northpole vector4
point attribute p@f
using a point wrangle node.
Now given the boundary polygon we want to have a surface p@f
which then can be normalized, it turns out to be better to remesh instead the stereographic projection which then can be mapped back to the
Furthermore we again need to identify interior points. This e.g. can be done by a sequence of group nodes:
Here the first group node just collects all the points by the base group option. The second only selects boundary vertices, which then are subtracted from the first group using a group combine node.
Now we are ready to iterate. This can be done by a loop node as we already used or—if one is interested in the progress of the iteration using a solver node
which performs one step per frame when then play button at the lower left corner is hit. The network inside look as follows:
Here the cotangent weight of the current immersion are computed using vertex wrangle nodes. After that we use point wrangle node on the interior point group to perform the iteration on the vector4
attribute p@f
. Here a sequence of frames how the results may look like:
The first picture shows the first frame while the last shows the 2000th.
After we solved the Plateau problem we can rotate and reflect the quadrilateral to form a larger piece. This is done here in a subnetwork which at the end spits out the stereographic projection
Here the point wrangle node reflect_in_e4_perp multiplies the 4th component of p@f by
p@f = set(p@f[0],-p@f[1],-p@f[3],p@f[2]);
The resulting patch can then be copied and rotated
Here you also see a null node to globally store the parameters
In particular we are now able to produce closed minimal surfaces in the
Homework (due 18 June). Build a network that computes for a given
Tutorial 8: Electric Fields on Surfaces
As described in the lecture a charge distribution
- Compute
and solve for the electric potential . - Compute its gradient and set
.
Here the resulting field for a distribution of positive and negative charge on the sphere:
Though there are some issues. First, we need first paint the density
A bit more serious problem is that not every density distribution leads to a solvable Poisson problem. Let us look a bit closer at this. We know that
Actually symmetric systems are also better for the numerics. Thus we solve instead
Below the electric potential on the buddy bear where we have placed a positive charge on its left upper paw and a negative charge under its right lower paw.
Actually the bear on the picture does not consist of triangles – we used a divide node to visualize the dual cells the piecewise constant functions live on.
The graph of the function @u
) can then be visualized by a polyextrude node. Therefore one selects Individual Elements in the Divide Into field and specifies the attribute u
in the Z scale field.
The dual surface also provides a way to visualize the electric field: Once one has computed the gradient (on the primitives of the triangle mesh) the divide node turns it into an attribute at the points of the dual surface (corresponding to the actual triangles). This can then be used to set up attributes @pscale
(length of gradient) and @orient
(quaternion that rotates say
vector g = v@grad; p@orient = dihedral(set(1,0,0),g); @pscale = length(g);
Here how the end of the network might look like:
And here the resulting image:
Since the potential varies quite smoothly the coloring from blue (minimum) to red (maximum) does not reveal much of the function. Another way to visualize hsvtorgb
, where we can stick in a scaled version of u
in the first slot (hue).
Homework (due 11 June). Build a network that allows to specify the charge on a given geometry and computes the corresponding electric field and potential.
Tutorial 7: The Discrete Plateau Problem
In the lecture we have defined what we mean by a discrete minimal surface. The goal of this tutorial is to visualize such minimal surfaces.
Let
In particular, each map
Unfortunately, unlike the Dirichlet problem, this is a non-linear problem—the cotangent weights depend on
The network above visualizes Plateau’s problem: Given a space curve
In the discrete setup we start with some discrete space, say generated by a curve node (probably followed by a resample node), which we then stick into a remesh node to get a reasonably triangulated disk spanned in. Then we can apply the iteration to this input. Below one possible result:
For rendering we used here the following texture in the mantra surface material. To generate texture coordinates one can use the nodes uvunwrap or uvtexture.
The algorithm is not restricted to disks. We can stick in any surface we want. Another nice example is e.g. obtained by starting with a cylinder of revolution. Here the minimal surface becomes a part of a catenoid or, if the cylinder’s height becomes to large, it degenerates to a pair of flat disks (connected by a straight line). In the picture below the last two circles are already too far apart – the surface starts to splits into two parts.
As a last example we can start with the unit sphere into which we cut several holes. If we want nice symmetries we could use a platonic solid node to copy smaller spheres to the vertex positions of e.g. an octahedron. The area enclosed by the smaller spheres we then subtract from the unit sphere with help of a cookie node. If we run then the algorithm on this input we end up with nice symmetric minimal surface. For a certain radius of the holes the surface is close to the Schwarz P surface.
Again one can observe the same effect as for the cylinder—if the holes get to small the minimal surface degenerates to several spheres connected to the origin.
Homework (due 4 June). Build a network that computes from a given discrete surface with boundary a minimal surface with the same boundary. Use this network to solve Plateau’s problem and to compute symmetric minimal surface from a sphere with holes as described above.
Tutorial 6: The Dirichet Problem
In the lecture we saw that the Dirichlet energy has a unique minimizer among all functions with prescribed boundary values. In this tutorial we want to visualize these minimizers in the discrete setting.
Let
To each discrete function
Further we have computed the Dirichlet energy of an affine function
Now let
To summarize: Given any function
Now we want to build up a network that computes the minimizer for given a function given on the boundary of a surface in
First we need to specify a surface in
The pointwrangle node contains the following 3 lines of code:
f@g = @P.y; v@P.y = 0; f@f;
Then we have a triangulated surface in the plane and a graph over its boundary (the curve we started with).
Now we want to build up the matrix
We will use a compressed sparse row matrix (scipy.sparse.csr_matrix). The constructor needs 3 arrays of equal length – two integer arrays (rows,colums) specifying the position of non-zero entries and a double array that specifies the non-zero entries – and two integers that specify the format of the matrix.
The non-zero entries in
By this observation above we can get a complete array of non-zero value indices as follows: we can store an attribute i@id = @ptnum
(@ptnum
itself is not accessible from python) on points and two attributes v@start
and v@end
(encoding row and column resp.) on Houdini primitive that contain for each edge in the primitive (starting from primhedge(geo,@primnum)
) the point number of the source point (@start
) and destination point (@end
) of the current edge in the Houndini primitive.
Here the VEX code of the primitivewrangle node:
int he = primhedge(0,@primnum); int p1 = hedge_srcpoint(0,he); int p2 = hedge_dstpoint(0,he); int p3 = hedge_presrcpoint(0,he); v@start = set(p1,p2,p3); v@end = set(p2,p3,p1);
The pointwrangle node just contains one line (i@id = @ptnum;
).
Before we can finally start to compute the cotangent weights and write the matrix entries to attributes we still need one more information – whether a point lies on the boundary or not. This we can do e.g. as follows:
The group node has in its edge tab a toggle to detect ‘unshared edges’, i.e. edge which appear in only one triangle. With this we build a group boundary
over whose points we then run with a pointwrangle node setting an attribute i@bd = 1
(the other points then have @bd = 0
per default).
Now let us start to compute the matrix entries. We will do this here in several steps.
Here VEX code of the wrangle nodes in their order:
int he = vertexhedge(0,@vtxnum); vector P = attrib(0,'point','P',hedge_srcpoint(0,he)); vector Q = attrib(0,'point','P',hedge_dstpoint(0,he)); f@edgelength = length(P-Q);
float getCosineAlpha(float a,b,c) { float d = b * b + c * c - a * a; float e = 2 * b * c; return d / e; } float getCotangentAlpha(float a,b,c) { float cosalpha = getCosineAlpha(a, b, c); float d = 1 - cosalpha * cosalpha; return cosalpha / sqrt(d); } int this = @vtxnum; int he = vertexhedge(0,this); int next = hedge_dstvertex(0,he); int prev = hedge_presrcvertex(0,he); float a = attrib(0,'vertex','edgelength',this); float b = attrib(0,'vertex','edgelength',next); float c = attrib(0,'vertex','edgelength',prev); f@ctan = getCotangentAlpha(a,b,c);
float getCotangentWeight(int geo; int he){ int opphe = hedge_nextequiv(geo,he); int v1 = hedge_srcvertex(geo,he); int v2 = hedge_srcvertex(geo,opphe); int bd1 = attrib(geo,'point','bd',vertexpoint(0,v1)); int bd2 = attrib(geo,'point','bd',vertexpoint(0,v2)); if(bd1 == 1){ return 0; }else{ // the startvertex determines the row the weight finally ends up // for boundary vertices all row entries up to the diagonal // entry shall vanish float cotanleft = attrib(geo,'vertex','ctan',v1); float cotanright = attrib(geo,'vertex','ctan',v2); return -(cotanleft+cotanright)/2.; } } int he = primhedge(0,@primnum); float c1 = getCotangentWeight(0,he); he = hedge_next(0,he); float c2 = getCotangentWeight(0,he); he = hedge_next(0,he); float c3 = getCotangentWeight(0,he); v@omega = set(c1,c2,c3);
if(attrib(0,'point','bd',@ptnum) == 1){ // diagonal entries corresponding to boundary values shall be 1 f@omega = 1.; }else{ // else summ the values on the vertex/edge star int he0 = pointhedge(0,@ptnum); int he = he0; int opphe; f@omega = 0; do{ opphe = hedge_nextequiv(0,he); // weights of Laplacian @omega += attrib(0,'vertex','ctan',hedge_srcvertex(0,he))/2.; @omega += attrib(0,'vertex','ctan',hedge_srcvertex(0,opphe))/2.; he = pointhedgenext(0,he); }while(he!=he0 && he!=-1); }
Now that we have set up the values we can build up the matrix in a python node: The attributes can read out of the Houdini geometry (geo) using methods as pointIntAttribValues(“attributename”) or primFloatAttribValues(“attributename”). The arrays are concatenated by numpy.append and then handed over to the csr_matrix-constructor. To access the matrix in the next node we can store it as cachedUserData in the node (compare the code below).
from scipy.sparse import csr_matrix import numpy as np import math ### get geometry node = hou.pwd() geo = node.geometry() n = len(geo.points()) ### get point and prim attributes pointid = np.array(geo.pointIntAttribValues("id")) start = np.array(geo.primIntAttribValues("start")) end = np.array(geo.primIntAttribValues("end")) omegaii = np.array(geo.pointFloatAttribValues("omega")) omegaij = np.array(geo.primFloatAttribValues("omega")) ### prepare matrix # non-zero matrix elements: # first the diagonal entries, then the entries corresponding to half edges rowids = np.append(pointid,start) colids = np.append(pointid,end) # values of Laplacian and mass corresponding to (row,col) specified above valueslaplacian = np.append(omegaii,omegaij) ### initialize matrices laplace = csr_matrix((valueslaplacian,(rowids,colids)),(n,n)) # set cached user data node.setCachedUserData("laplace",laplace)
We still need to prepare the right hand side of the system. Therefore we extend the function
Here the corresponding code
if(@bd == 0) @g= 0;
import numpy node = hou.pwd() g = numpy.array(node.geometry().pointFloatAttribValues("g")) node.setCachedUserData("g", g)
Now we stick both python node – the one in which we stored the matrix and the one in which we stored the right hand side – into another one where we finally solve the linear system.
The cached user data can be accessed by its name. The easiest way to solve the system is to use scipy.sparse.linalg.spsolve. After that the solution is stored as a point attribute. Below the corresponding python code:
from scipy.sparse.linalg import spsolve import numpy node = hou.pwd() geo = node.geometry() laplace = node.inputs()[0].cachedUserData("laplace") g = node.inputs()[1].cachedUserData("g") f = spsolve(laplace,g) geo.setPointFloatAttribValuesFromString("f",f.astype(numpy.float32))
Though this can be speeded up when rewriting the system as an inhomogeneous symmetric system, which can then be solved by scipy.sparse.linalg.cg. Here the alternative code:
from scipy.sparse.linalg import cg import numpy as np node = hou.pwd() geo = node.geometry() isbd = np.array(geo.pointIntAttribValues("bd"),dtype="bool") isin = np.logical_not(isbd) laplace = node.inputs()[0].cachedUserData("laplace") g = node.inputs()[1].cachedUserData("g") laplaceI = laplace[isin,:] laplaceII = laplaceI[:,isin] laplaceIB = laplaceI[:,isbd] gb = g[isbd] x,tmp = cg(laplaceII,-laplaceIB.dot(gb)) f = g f[isin] = x geo.setPointFloatAttribValuesFromString("f",f.astype(np.float32))
Now we are back in VEX land and we can finally draw the graph of the solution:
Homework (due 28 May). Build a network which allows to prescribe boundary values and then computes the corresponding minimizer of the Dirichlet energy.
Plateau problem
Let
If we have a realization of the surface in
Definition: A metric on a triangulated surface assigns a length to each oriented edge such that the triangle inequalities are satisfied.
Note that every realization
In particular, we can define angles and area using the cosine theorem:
By the triangle inequalities we obtain that
For a realization
and the total area is give by:
Definition: The following problem is the Plateau problem: Let
1.
2.
In order to be able to solve the Plateau problem we have to consider variations of
Definition: A variation of
1.
2.
Definition:
for all variations of
Lemma:
I.e.
Proof:
All directional derivatives of a function
Theorem:
Proof: Fix
Before we start with the proof remember these identities from analysis
1. For a function
2. For
With
With
and
We have:
In an earlier lecture we showed that:
With an index shift in the first sum
Since this has to hold for all variations
This means