Curvature.h
/*
method taken from:
Princeton University file 'TriMesh_curvature.cc'
Rusinkiewicz, Szymon. "Estimating Curvatures and Their Derivatives on Triangle Meshes," 2004
*/
#pragma once
#include "SurfaceMeshModel.h"
// Macros needed for Princeton code:
// i+1 and i-1 modulo 3
// This way of computing it tends to be faster than using %
#define NEXT_Index(i) ((i)<2 ? (i)+1 : (i)-2)
#define PREV_Index(i) ((i)>0 ? (i)-1 : (i)+2)
// Let gcc optimize conditional branches a bit better...
#ifndef likely
# if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
# define likely(x) (x)
# define unlikely(x) (x)
# else
# define likely(x) (__builtin_expect((x), 1))
# define unlikely(x) (__builtin_expect((x), 0))
# endif
#endif
// Perform LDL^T decomposition of a symmetric positive definite matrix.
// Like Cholesky, but no square roots. Overwrites lower triangle of matrix.
template <class T, int N>
static inline bool ldltdc(T A[N][N], T rdiag[N])
{
T v[N-1];
for (int i = 0; i < N; i++) {
for (int k = 0; k < i; k++)
v[k] = A[i][k] * rdiag[k];
for (int j = i; j < N; j++) {
T sum = A[i][j];
for (int k = 0; k < i; k++)
sum -= v[k] * A[j][k];
if (i == j) {
if (unlikely(sum <= T(0)))
return false;
rdiag[i] = T(1) / sum;
} else {
A[j][i] = sum;
}
}
}
return true;
}
// Solve Ax=B after ldltdc
template <class T, int N>
static inline void ldltsl(T A[N][N], T rdiag[N], T B[N], T x[N])
{
int i;
for (i = 0; i < N; i++) {
T sum = B[i];
for (int k = 0; k < i; k++)
sum -= A[i][k] * x[k];
x[i] = sum * rdiag[i];
}
for (i = N - 1; i >= 0; i--) {
T sum = 0;
for (int k = i + 1; k < N; k++)
sum += A[k][i] * x[k];
x[i] -= sum * rdiag[i];
}
}
// Stuff
#define SWAP(x, y, T) do { T temp##x##y = x; x = y; y = temp##x##y; } while (0)
namespace SurfaceMesh{
class Curvature
{
private:
std::vector<double> curv1, curv2;
std::vector<Vec4d> dcurv;
std::vector<Vec3d> pdir1, pdir2;
std::vector<Vec3d> cornerareas;
std::vector<double> pointareas;
public:
// from 'TriMesh2' by "Szymon Rusinkiewicz" - Finite-differences approach
void computePrincipalCurvatures(Surface_mesh * src_mesh);
// derivatives of curvature
void computeDerivativesCurvatures(Surface_mesh * src_mesh);
// Compute per-vertex point areas
void computePointAreas(Surface_mesh * src_mesh);
void rot_coord_sys(const Point &old_u, const Point &old_v, const Point &new_norm, Point &new_u, Point &new_v);
void proj_curv(const Point &old_u, const Point &old_v, double old_ku, double old_kuv, double old_kv,
const Point &new_u, const Point &new_v, double &new_ku, double &new_kuv, double &new_kv);
void proj_dcurv(const Point &old_u, const Point &old_v, const Vec4d old_dcurv,
const Point &new_u, const Point &new_v, Vec4d & new_dcurv);
void diagonalize_curv(const Point &old_u, const Point &old_v, double ku, double kuv, double kv,
const Point &new_norm, Point &pdir1, Point &pdir2, double &k1, double &k2);
};
// Rotate a coordinate system to be perpendicular to the given normal
void Curvature::rot_coord_sys( const Point &old_u, const Point &old_v, const Point &new_norm, Point &new_u, Point &new_v )
{
new_u = old_u;
new_v = old_v;
Point old_norm = cross(old_u, old_v);
double ndot = dot(old_norm, new_norm);
if (unlikely(ndot <= -1.0f)) {
new_u = -new_u;
new_v = -new_v;
return;
}
Point perp_old = new_norm - ndot * old_norm;
Point dperp = 1.0f / (1 + ndot) * (old_norm + new_norm);
new_u -= dperp * dot(new_u , perp_old);
new_v -= dperp * dot(new_v , perp_old);
}
// Re-project a curvature tensor from the basis spanned by old_u and old_v
// (which are assumed to be unit-length and perpendicular) to the
// new_u, new_v basis.
void Curvature::proj_curv( const Point &old_u, const Point &old_v, double old_ku, double old_kuv, double old_kv,
const Point &new_u, const Point &new_v, double &new_ku, double &new_kuv, double &new_kv )
{
Point r_new_u, r_new_v;
rot_coord_sys(new_u, new_v, cross(old_u, old_v), r_new_u, r_new_v);
double u1 = dot(r_new_u, old_u);
double v1 = dot(r_new_u, old_v);
double u2 = dot(r_new_v, old_u);
double v2 = dot(r_new_v, old_v);
new_ku = old_ku * u1*u1 + old_kuv * (2.0f * u1*v1) + old_kv * v1*v1;
new_kuv = old_ku * u1*u2 + old_kuv * (u1*v2 + u2*v1) + old_kv * v1*v2;
new_kv = old_ku * u2*u2 + old_kuv * (2.0f * u2*v2) + old_kv * v2*v2;
}
// And for derivatives of curvature
void Curvature::proj_dcurv(const Point &old_u, const Point &old_v, const Vec4d old_dcurv,
const Point &new_u, const Point &new_v, Vec4d & new_dcurv)
{
Point r_new_u, r_new_v;
rot_coord_sys(new_u, new_v, cross(old_u, old_v), r_new_u, r_new_v);
double u1 = dot(r_new_u, old_u);
double v1 = dot(r_new_u, old_v);
double u2 = dot(r_new_v, old_u);
double v2 = dot(r_new_v, old_v);
new_dcurv[0] = old_dcurv[0]*u1*u1*u1 + old_dcurv[1]*3.0f*u1*u1*v1 + old_dcurv[2]*3.0f*u1*v1*v1 + old_dcurv[3]*v1*v1*v1;
new_dcurv[1] = old_dcurv[0]*u1*u1*u2 + old_dcurv[1]*(u1*u1*v2 + 2.0f*u2*u1*v1) + old_dcurv[2]*(u2*v1*v1 + 2.0f*u1*v1*v2) + old_dcurv[3]*v1*v1*v2;
new_dcurv[2] = old_dcurv[0]*u1*u2*u2 + old_dcurv[1]*(u2*u2*v1 + 2.0f*u1*u2*v2) + old_dcurv[2]*(u1*v2*v2 + 2.0f*u2*v2*v1) + old_dcurv[3]*v1*v2*v2;
new_dcurv[3] = old_dcurv[0]*u2*u2*u2 + old_dcurv[1]*3.0f*u2*u2*v2 + old_dcurv[2]*3.0f*u2*v2*v2 + old_dcurv[3]*v2*v2*v2;
}
// Given a curvature tensor, find principal directions and curvatures
// Makes sure that pdir1 and pdir2 are perpendicular to normal
void Curvature::diagonalize_curv( const Point &old_u, const Point &old_v, double ku, double kuv, double kv,
const Point &new_norm, Point &pdir1, Point &pdir2, double &k1, double &k2 )
{
Point r_old_u, r_old_v;
rot_coord_sys(old_u, old_v, new_norm, r_old_u, r_old_v);
double c = 1, s = 0, tt = 0;
if (likely(kuv != 0.0f))
{
// Jacobi rotation to diagonalize
double h = 0.5f * (kv - ku) / kuv;
tt = (h < 0.0f) ? 1.0f / (h - sqrt(1.0f + h*h)) : 1.0f / (h + sqrt(1.0f + h*h));
c = 1.0f / sqrt(1.0f + tt*tt);
s = tt * c;
}
k1 = ku - tt * kuv;
k2 = kv + tt * kuv;
if (fabs(k1) >= fabs(k2)) {
pdir1 = c*r_old_u - s*r_old_v;
} else {
SWAP(k1, k2, double);
pdir1 = s*r_old_u + c*r_old_v;
}
pdir2 = cross(new_norm, pdir1);
}
void Curvature::computePrincipalCurvatures( Surface_mesh * src_mesh )
{
computePointAreas(src_mesh);
uint nv = src_mesh->n_vertices(), nf = src_mesh->n_faces();
curv1.resize(nv);
curv2.resize(nv);
pdir1.resize(nv);
pdir2.resize(nv);
std::vector<double> curv12(nv);
Surface_mesh::Vertex_property<Point> points = src_mesh->vertex_property<Point>("v:point");
Surface_mesh::Vertex_property<Normal> normals = src_mesh->vertex_property<Normal>("v:normal");
Surface_mesh::Face_iterator fit, fend = src_mesh->faces_end();
Surface_mesh::Vertex_around_face_circulator fvit, fvend;
Surface_mesh::Vertex_iterator vit, vend = src_mesh->vertices_end();
//#pragma omp parallel for
for (fit = src_mesh->faces_begin(); fit != fend; ++fit)
{
fvit = src_mesh->vertices(fit);
uint vi0 = ((Vertex)fvit).idx(); Point v0 = points[fvit]; ++fvit;
uint vi1 = ((Vertex)fvit).idx(); Point v1 = points[fvit]; ++fvit;
uint vi2 = ((Vertex)fvit).idx(); Point v2 = points[fvit];
pdir1[vi0] = v1 - v0;
pdir1[vi1] = v2 - v1;
pdir1[vi2] = v0 - v2;
}
//#pragma omp parallel for
for (vit = src_mesh->vertices_begin(); vit != vend; ++vit)
{
uint vi = ((Vertex)vit).idx();
pdir1[vi] = cross(pdir1[vi], normals[vit]);
pdir1[vi].normalize();
pdir2[vi] = cross(normals[vit], pdir1[vi]);
}
// Compute curvature per-face
//#pragma omp parallel for
for (fit = src_mesh->faces_begin(); fit != fend; ++fit)
{
uint i = ((Surface_mesh::Face)fit).idx(); // face index
uint vi[3]; Normal vn[3];
fvit = src_mesh->vertices(fit);
vi[0] = ((Vertex)fvit).idx(); Point v0 = points[fvit]; vn[0] = normals[fvit]; ++fvit;
vi[1] = ((Vertex)fvit).idx(); Point v1 = points[fvit]; vn[1] = normals[fvit]; ++fvit;
vi[2] = ((Vertex)fvit).idx(); Point v2 = points[fvit]; vn[2] = normals[fvit]; ++fvit;
// Edges
Point e[3] = {v2 - v1, v0 - v2, v1 - v0};
// N-T-B coordinate system per face
Point t = e[0];
t.normalize();
Point n = cross(e[0], e[1]);
Point b = cross(n, t);
b.normalize();
// Estimate curvature based on variation of normals
// along edges
double m[3] = { 0, 0, 0 };
double w[3][3] = { {0,0,0}, {0,0,0}, {0,0,0} };
for (int j = 0; j < 3; j++) {
double u = dot(e[j], t);
double v = dot(e[j], b);
w[0][0] += u*u;
w[0][1] += u*v;
//w[1][1] += v*v + u*u;
//w[1][2] += u*v;
w[2][2] += v*v;
Point dn = vn[PREV_Index(j)] - vn[NEXT_Index(j)];
double dnu = dot(dn, t);
double dnv = dot(dn, b);
m[0] += dnu*u;
m[1] += dnu*v + dnv*u;
m[2] += dnv*v;
}
w[1][1] = w[0][0] + w[2][2];
w[1][2] = w[0][1];
// Least squares solution
double diag[3];
if (!ldltdc<double,3>(w, diag)) {
//printf("ldltdc failed!\n");
continue;
}
ldltsl<double,3>(w, diag, m, m);
// Push it back out to the vertices
for (uint j = 0; j < 3; j++)
{
int vj = vi[j];
double c1, c12, c2;
proj_curv(t, b, m[0], m[1], m[2], pdir1[vj], pdir2[vj], c1, c12, c2);
double wt = cornerareas[i][j] / pointareas[vj];
//#pragma omp atomic
curv1[vj] += wt * c1;
//#pragma omp atomic
curv12[vj] += wt * c12;
//#pragma omp atomic
curv2[vj] += wt * c2;
}
}
// Store results into Surface_mesh object
Surface_mesh::Vertex_property<double> my_curv1 = src_mesh->vertex_property<double>("v:curv1");
Surface_mesh::Vertex_property<double> my_curv2 = src_mesh->vertex_property<double>("v:vurv2");
Surface_mesh::Vertex_property<Vec3d> my_pdir1 = src_mesh->vertex_property<Vec3d>("v:pdir1");
Surface_mesh::Vertex_property<Vec3d> my_pdir2 = src_mesh->vertex_property<Vec3d>("v:pdir2");
// Compute principal directions and curvatures at each vertex
//#pragma omp parallel for
for (vit = src_mesh->vertices_begin(); vit != vend; ++vit)
{
uint i = ((Surface_mesh::Vertex)vit).idx();
diagonalize_curv(pdir1[i], pdir2[i],curv1[i], curv12[i], curv2[i], normals[vit], pdir1[i], pdir2[i],curv1[i], curv2[i]);
my_curv1[vit] = curv1[i];
my_curv2[vit] = curv2[i];
my_pdir1[vit] = pdir1[i];
my_pdir2[vit] = pdir2[i];
}
}
// Compute derivatives of curvature
void Curvature::computeDerivativesCurvatures(Surface_mesh * src_mesh)
{
// Compute principal curvatures and directions
computePrincipalCurvatures(src_mesh);
Surface_mesh::Vertex_property<Point> points = src_mesh->vertex_property<Point>("v:point");
Surface_mesh::Vertex_property<Normal> normals = src_mesh->vertex_property<Normal>("v:normal");
Surface_mesh::Face_iterator fit, fend = src_mesh->faces_end();
Surface_mesh::Vertex_around_face_circulator fvit, fvend;
// Resize the arrays we'll be using
uint nv = src_mesh->n_vertices(), nf = src_mesh->n_faces();
dcurv.resize(nv);
// Compute derivatives of curvature per-face
//#pragma omp parallel for
for (fit = src_mesh->faces_begin(); fit != fend; ++fit)
{
uint i = ((Surface_mesh::Face)fit).idx(); // face index
uint vi[3]; Normal vn[3];
fvit = src_mesh->vertices(fit);
vi[0] = ((Vertex)fvit).idx(); Point v0 = points[fvit]; vn[0] = normals[fvit]; ++fvit;
vi[1] = ((Vertex)fvit).idx(); Point v1 = points[fvit]; vn[1] = normals[fvit]; ++fvit;
vi[2] = ((Vertex)fvit).idx(); Point v2 = points[fvit]; vn[2] = normals[fvit]; ++fvit;
// Edges
Point e[3] = {v2 - v1, v0 - v2, v1 - v0};
// N-T-B coordinate system per face
Point t = e[0];
t.normalize();
Point n = cross(e[0], e[1]);
Point b = cross(n, t);
b.normalize();
// Project curvature tensor from each vertex into this
// face's coordinate system
Point fcurv[3];
for (int j = 0; j < 3; j++)
{
int vj = vi[j];
proj_curv(pdir1[vj], pdir2[vj], curv1[vj], 0, curv2[vj],t, b, fcurv[j][0], fcurv[j][1], fcurv[j][2]);
}
// Estimate derivatives of curvature based on variation of curvature along edges
double m[4] = {0,0,0,0};
double w[4][4] = { {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0} };
for (int j = 0; j < 3; j++)
{
// Variation of curvature along each edge
Point dfcurv = fcurv[PREV_Index(j)] - fcurv[NEXT_Index(j)];
double u = dot(e[j], t);
double v = dot(e[j], b);
double u2 = u*u, v2 = v*v, uv = u*v;
w[0][0] += u2;
w[0][1] += uv;
//w[1][1] += 2.0f*u2 + v2;
//w[1][2] += 2.0f*uv;
//w[2][2] += u2 + 2.0f*v2;
//w[2][3] += uv;
w[3][3] += v2;
m[0] += u*dfcurv[0];
m[1] += v*dfcurv[0] + 2.0f*u*dfcurv[1];
m[2] += 2.0f*v*dfcurv[1] + u*dfcurv[2];
m[3] += v*dfcurv[2];
}
w[1][1] = 2.0f * w[0][0] + w[3][3];
w[1][2] = 2.0f * w[0][1];
w[2][2] = w[0][0] + 2.0f * w[3][3];
w[2][3] = w[0][1];
// Least squares solution
double d[4];
if (!ldltdc<double,4>(w, d))
{
//printf("ldltdc failed!\n");
continue;
}
ldltsl<double,4>(w, d, m, m);
Vec4d face_dcurv(m[0], m[1], m[2], m[3]);
// Push it back out to each vertex
for (int j = 0; j < 3; j++)
{
int vj = vi[j];
Vec4d this_vert_dcurv;
proj_dcurv(t, b, face_dcurv, pdir1[vj], pdir2[vj], this_vert_dcurv);
double wt = cornerareas[i][j] / pointareas[vj];
dcurv[vj] += wt * this_vert_dcurv;
}
}
Surface_mesh::Vertex_property<Vec4d> my_dcurv = src_mesh->vertex_property<Vec4d>("v:dcurv");
Surface_mesh::Vertex_iterator vit, vend = src_mesh->vertices_end();
for (vit = src_mesh->vertices_begin(); vit != vend; ++vit){
uint i = ((Vertex) vit).idx();
my_dcurv[vit] = dcurv[i];
}
}
/* Compute the area "belonging" to each vertex or each corner of a triangle
(defined as Voronoi area restricted to the 1-ring of a vertex, or to the triangle).*/
void Curvature::computePointAreas(Surface_mesh * src_mesh)
{
// Get from the Surface_mesh everything you need
Surface_mesh::Vertex_property<Point> points = src_mesh->vertex_property<Point>("v:point");
Surface_mesh::Face_iterator fit, fend = src_mesh->faces_end();
Surface_mesh::Vertex_around_face_circulator fvit, fvend;
cornerareas = std::vector<Vec3d>(src_mesh->n_faces());
pointareas = std::vector<double>(src_mesh->n_vertices());
//#pragma omp parallel for
for (fit = src_mesh->faces_begin(); fit != fend; ++fit)
{
uint vi[3];
fvit = src_mesh->vertices(fit);
vi[0] = ((Vertex)fvit).idx(); Point v0 = points[fvit]; ++fvit;
vi[1] = ((Vertex)fvit).idx(); Point v1 = points[fvit]; ++fvit;
vi[2] = ((Vertex)fvit).idx(); Point v2 = points[fvit]; ++fvit;
// Edges
Point e[3] = {v2 - v1, v0 - v2, v1 - v0};
// Compute corner weights
double area = 0.5f * cross(e[0], e[1]).norm();
double squaredNorm_e0 = e[0].norm() * e[0].norm();
double squaredNorm_e1 = e[1].norm() * e[1].norm();
double squaredNorm_e2 = e[2].norm() * e[2].norm();
double l2[3] = { squaredNorm_e0, squaredNorm_e1, squaredNorm_e2};
double ew[3] = { l2[0] * (l2[1] + l2[2] - l2[0]), l2[1] * (l2[2] + l2[0] - l2[1]), l2[2] * (l2[0] + l2[1] - l2[2]) };
uint i = ((Surface_mesh::Face) fit).idx();
if (ew[0] <= 0.0f) {
cornerareas[i][1] = -0.25f * l2[2] * area / dot(e[0] , e[2]);
cornerareas[i][2] = -0.25f * l2[1] * area / dot(e[0] , e[1]);
cornerareas[i][0] = area - cornerareas[i][1] - cornerareas[i][2];
} else if (ew[1] <= 0.0f) {
cornerareas[i][2] = -0.25f * l2[0] * area / dot(e[1] , e[0]);
cornerareas[i][0] = -0.25f * l2[2] * area / dot(e[1] , e[2]);
cornerareas[i][1] = area - cornerareas[i][2] - cornerareas[i][0];
} else if (ew[2] <= 0.0f) {
cornerareas[i][0] = -0.25f * l2[1] * area / dot(e[2] , e[1]);
cornerareas[i][1] = -0.25f * l2[0] * area / dot(e[2] , e[0]);
cornerareas[i][2] = area - cornerareas[i][0] - cornerareas[i][1];
} else {
double ewscale = 0.5f * area / (ew[0] + ew[1] + ew[2]);
for (int j = 0; j < 3; j++)
{
cornerareas[i][j] = ewscale * (ew[(j+1)%3] + ew[(j+2)%3]);
}
}
//#pragma omp atomic
pointareas[vi[0]] += cornerareas[i][0];
//#pragma omp atomic
pointareas[vi[1]] += cornerareas[i][1];
//#pragma omp atomic
pointareas[vi[2]] += cornerareas[i][2];
}
}
}