Skip to content

Commit 5adae5b

Browse files
authored
Add marching triangles (#218)
* Marching triangles to contour scalar functions * Fixes a sign swap in the signed version of fast marching * Fixes a bug in the inEdge() function of the SurfacePoint class
1 parent b0a0548 commit 5adae5b

10 files changed

Lines changed: 176 additions & 30 deletions

File tree

docs/docs/surface/utilities/barycentric_vector.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ which indicates what kind of vector it is.
2525
2626
??? func "`#!cpp BarycentricVector::BarycentricVector(Face f, Vector3 faceCoords)`"
2727
28-
Construct a barycentric vector that lies along the given face `f`, with the given barycentric coordinates `faceCoords`.
28+
Construct a barycentric vector that lies in the given face `f`, with the given barycentric coordinates `faceCoords`.
2929
3030
??? func "`#!cpp BarycentricVector::BarycentricVector(Edge e, Vector2 edgeCoords)`"
3131
@@ -35,6 +35,10 @@ which indicates what kind of vector it is.
3535
3636
Construct a (zero-length) barycentric vector that lies on the given vertex `v`.
3737
38+
??? func "`#!cpp BarycentricVector::BarycentricVector(Halfedge he, Face f)`"
39+
40+
Construct a barycentric vector from the given halfedge, that lies in the given face `f`.
41+
3842
??? func "`#!cpp BarycentricVector::BarycentricVector(SurfacePoint pA, SurfacePoint pB)`"
3943
4044
Construct a barycentric vector from the given [surface points](../surface_point), i.e. barycentric points. The vector direction goes from `pA` to `pB`.

include/geometrycentral/surface/barycentric_vector.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct BarycentricVector {
2020
BarycentricVector(Vertex v);
2121
BarycentricVector(Edge e, Vector2 edgeCoords);
2222
BarycentricVector(Face f, Vector3 faceCoords);
23+
BarycentricVector(Halfedge he, Face f);
2324

2425
BarycentricVectorType type = BarycentricVectorType::Face;
2526

include/geometrycentral/surface/barycentric_vector.ipp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ inline BarycentricVector::BarycentricVector(SurfacePoint pA, SurfacePoint pB) {
4444
faceCoords = pB_in_face.faceCoords - pA_in_face.faceCoords;
4545
}
4646

47+
inline BarycentricVector::BarycentricVector(Halfedge he_, Face f) : type(BarycentricVectorType::Face), face(f) {
48+
49+
int eIdx = 0;
50+
double sign = 0.;
51+
for (Halfedge he : f.adjacentHalfedges()) {
52+
if (he.edge() == he_.edge()) {
53+
sign = (he.tailVertex() == he_.tailVertex() && he.tipVertex() == he_.tipVertex()) ? 1. : -1.;
54+
break;
55+
}
56+
eIdx++;
57+
}
58+
faceCoords = {0, 0, 0};
59+
faceCoords[(eIdx + 1) % 3] = 1;
60+
faceCoords[eIdx] = -1;
61+
faceCoords *= sign;
62+
}
63+
4764
// == Methods
4865

4966
inline BarycentricVector BarycentricVector::inSomeFace() const {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include "geometrycentral/surface/barycentric_vector.h"
4+
5+
namespace geometrycentral {
6+
namespace surface {
7+
8+
std::vector<std::vector<SurfacePoint>> marchingTriangles(IntrinsicGeometryInterface& geom, const VertexData<double>& u,
9+
double isoval = 0.);
10+
11+
}
12+
} // namespace geometrycentral

include/geometrycentral/surface/signed_heat_method.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ class SignedHeatSolver {
8080
double lengthOfSegment(const SurfacePoint& pA, const SurfacePoint& pB) const;
8181
SurfacePoint midSegmentSurfacePoint(const SurfacePoint& pA, const SurfacePoint& pB) const;
8282
std::complex<double> projectedNormal(const SurfacePoint& pA, const SurfacePoint& pB, const Edge& e) const;
83-
BarycentricVector barycentricVectorInFace(const Halfedge& he, const Face& f) const;
8483
FaceData<BarycentricVector> sampleAtFaceBarycenters(const Vector<std::complex<double>>& Xt);
8584
Vector<double> integrateWithZeroSetConstraint(const Vector<double>& rhs, const std::vector<Curve>& curves,
8685
const std::vector<SurfacePoint>& points,

include/geometrycentral/surface/surface_point.ipp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ inline SurfacePoint SurfacePoint::inEdge(Edge targetEdge) const {
151151
switch (type) {
152152
case SurfacePointType::Vertex:
153153
if (vertex == targetEdge.halfedge().tailVertex()) {
154-
return SurfacePoint(targetEdge, 0);
155-
} else if (vertex == targetEdge.halfedge().tipVertex()) {
156154
return SurfacePoint(targetEdge, 1);
155+
} else if (vertex == targetEdge.halfedge().tipVertex()) {
156+
return SurfacePoint(targetEdge, 0);
157157
}
158158
break;
159159
case SurfacePointType::Edge:

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ SET(SRCS
2626
surface/embedded_geometry_interface.cpp
2727
surface/edge_length_geometry.cpp
2828
surface/vertex_position_geometry.cpp
29+
surface/marching_triangles.cpp
2930
surface/mutation_manager.cpp
3031
surface/mesh_graph_algorithms.cpp
3132
surface/direction_fields.cpp

src/surface/fast_marching_method.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,17 @@ VertexData<double> FMMDistance(IntrinsicGeometryInterface& geometry,
9292
// Assign +/- signs to the "third" vertices of each face straddling this edge.
9393
// These vertices might themselves lie on the curve, in which case we overwrite them below.
9494
Halfedge he = commonEdge.halfedge();
95-
signs[he.next().tipVertex()] = (he.vertex() == pA.vertex) ? 1 : -1;
95+
signs[he.next().tipVertex()] = (he.vertex() == pA.vertex) ? -1 : 1;
9696
signs[he.twin().next().tipVertex()] = -signs[he.next().tipVertex()];
9797
} else {
9898
Face commonFace = sharedFace(pA, pB);
9999
if (commonFace == Face()) {
100100
throw std::logic_error("For signed fast marching distance, each curve segment must share a common face.");
101101
}
102102
BarycentricVector tangent(pA, pB);
103-
BarycentricVector normal = tangent.rotate90(geometry);
103+
BarycentricVector normal = -tangent.rotate90(geometry);
104104
for (Vertex v : commonFace.adjacentVertices()) {
105-
BarycentricVector u(SurfacePoint(v), pA);
105+
BarycentricVector u(pA, SurfacePoint(v));
106106
signs[v] = (dot(geometry, normal, u) > 0) ? 1 : -1;
107107
}
108108
}

src/surface/marching_triangles.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#include "geometrycentral/surface/marching_triangles.h"
2+
3+
namespace geometrycentral {
4+
namespace surface {
5+
6+
namespace {
7+
8+
std::vector<std::vector<std::array<size_t, 2>>>
9+
getCurveComponents(SurfaceMesh& mesh, const std::vector<SurfacePoint>& curveNodes,
10+
const std::vector<std::array<size_t, 2>>& curveEdges) {
11+
12+
// Note: This function assumes that `curveNodes` has been de-duplicated.
13+
std::vector<std::array<size_t, 2>> edgesToAdd = curveEdges;
14+
std::vector<std::vector<std::array<size_t, 2>>> curves;
15+
size_t nSegs = curveEdges.size();
16+
while (edgesToAdd.size() > 0) {
17+
std::array<size_t, 2> startSeg = edgesToAdd.back();
18+
edgesToAdd.pop_back();
19+
curves.emplace_back();
20+
std::vector<std::array<size_t, 2>>& currCurve = curves.back();
21+
currCurve.push_back(startSeg);
22+
23+
// Add segs to the front end until we can't.
24+
std::array<size_t, 2> currSeg = startSeg;
25+
while (true) {
26+
const SurfacePoint& front = curveNodes[currSeg[1]];
27+
bool didWeFindOne = false;
28+
for (size_t i = 0; i < edgesToAdd.size(); i++) {
29+
std::array<size_t, 2> otherSeg = edgesToAdd[i];
30+
if (curveNodes[otherSeg[0]] == front) {
31+
currSeg = otherSeg;
32+
currCurve.push_back(otherSeg);
33+
edgesToAdd.erase(edgesToAdd.begin() + i);
34+
didWeFindOne = true;
35+
break;
36+
}
37+
}
38+
if (!didWeFindOne) break;
39+
}
40+
41+
// Add segs to the back end until we can't.
42+
currSeg = startSeg;
43+
while (true) {
44+
const SurfacePoint& back = curveNodes[currSeg[0]];
45+
bool didWeFindOne = false;
46+
for (size_t i = 0; i < edgesToAdd.size(); i++) {
47+
std::array<size_t, 2> otherSeg = edgesToAdd[i];
48+
if (curveNodes[otherSeg[1]] == back) {
49+
currSeg = otherSeg;
50+
currCurve.insert(currCurve.begin(), otherSeg);
51+
edgesToAdd.erase(edgesToAdd.begin() + i);
52+
didWeFindOne = true;
53+
break;
54+
}
55+
}
56+
if (!didWeFindOne) break;
57+
}
58+
}
59+
return curves;
60+
}
61+
} // namespace
62+
63+
std::vector<std::vector<SurfacePoint>> marchingTriangles(IntrinsicGeometryInterface& geom, const VertexData<double>& u,
64+
double isoval) {
65+
66+
SurfaceMesh& mesh = *u.getMesh();
67+
std::vector<SurfacePoint> nodes;
68+
std::vector<std::array<size_t, 2>> edges;
69+
for (Face f : mesh.faces()) {
70+
std::vector<SurfacePoint> hits;
71+
BarycentricVector gradient(f);
72+
for (Halfedge he : f.adjacentHalfedges()) {
73+
// Record edge crossings
74+
Edge e = he.edge();
75+
Vertex v0 = e.firstVertex();
76+
Vertex v1 = e.secondVertex();
77+
double u0 = u[v0];
78+
double u1 = u[v1];
79+
double lB = std::min(u0, u1);
80+
double uB = std::max(u0, u1);
81+
if (lB == uB && lB == isoval) {
82+
hits.clear();
83+
hits = {SurfacePoint(v0), SurfacePoint(v1)};
84+
break;
85+
}
86+
double t = (isoval - lB) / (uB - lB);
87+
if (u0 > u1) t = 1. - t;
88+
if (t <= 1. && t >= 0.) hits.emplace_back(e, t);
89+
// Compute gradient of the scalar function.
90+
BarycentricVector heVec(he.next(), f);
91+
BarycentricVector ePerp = heVec.rotate90(geom);
92+
gradient += ePerp * u[he.vertex()];
93+
}
94+
if (hits.size() != 2) continue;
95+
96+
// Orient segments so that smaller values are always on the "inside" of the curve.
97+
std::array<size_t, 2> seg;
98+
for (int i = 0; i < 2; i++) {
99+
auto iter = std::find(nodes.begin(), nodes.end(), hits[i]);
100+
if (iter != nodes.end()) {
101+
seg[i] = iter - nodes.begin();
102+
} else {
103+
nodes.push_back(hits[i]);
104+
seg[i] = nodes.size() - 1;
105+
}
106+
}
107+
BarycentricVector segTangent(hits[0], hits[1]);
108+
BarycentricVector segNormal = -segTangent.inFace(f).rotate90(geom);
109+
if (dot(geom, segNormal, gradient) > 0.) {
110+
edges.push_back(seg);
111+
} else {
112+
edges.push_back({seg[1], seg[0]});
113+
}
114+
}
115+
std::vector<std::vector<std::array<size_t, 2>>> components = getCurveComponents(mesh, nodes, edges);
116+
size_t nCurves = components.size();
117+
std::vector<std::vector<SurfacePoint>> curves(nCurves);
118+
for (size_t i = 0; i < nCurves; i++) {
119+
size_t nEdges = components[i].size();
120+
for (size_t j = 0; j < nEdges; j++) {
121+
curves[i].push_back(nodes[components[i][j][0]]);
122+
}
123+
curves[i].push_back(nodes[components[i][nEdges - 1][1]]);
124+
}
125+
return curves;
126+
}
127+
128+
} // namespace surface
129+
} // namespace geometrycentral

src/surface/signed_heat_method.cpp

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Vector<std::complex<double>> SignedHeatSolver::integrateVectorHeatFlow(const std
113113
for (Edge e : f.adjacentEdges()) {
114114
size_t eIdx = geom.edgeIndices[e];
115115
double w = scalarCrouzeixRaviart(b, e);
116-
BarycentricVector heVec = barycentricVectorInFace(e.halfedge(), f);
116+
BarycentricVector heVec(e.halfedge(), f);
117117
heVec /= heVec.norm(geom);
118118
BarycentricVector heVecN = heVec.rotate90(geom);
119119
triplets.emplace_back(m, eIdx, w * dot(geom, heVec, segTangent));
@@ -162,8 +162,8 @@ VertexData<double> SignedHeatSolver::integrateVectorField(const Vector<std::comp
162162
Halfedge heA = c.halfedge();
163163
Halfedge heB = heA.next().next();
164164
BarycentricVector Yj = Y[f];
165-
BarycentricVector e1 = barycentricVectorInFace(heA, f);
166-
BarycentricVector e2 = -barycentricVectorInFace(heB, f);
165+
BarycentricVector e1(heA, f);
166+
BarycentricVector e2 = -BarycentricVector(heB, f);
167167
double cotTheta1 = geom.halfedgeCotanWeights[heA];
168168
double cotTheta2 = geom.halfedgeCotanWeights[heB];
169169
double w1 = cotTheta1 * dot(geom, e1, Yj);
@@ -252,11 +252,11 @@ void SignedHeatSolver::buildUnsignedCurveSource(const Curve& curve, Vector<std::
252252
// Ordinarily, "double-sided" vector information would cancel out along the edge. So in each face, "smear"
253253
// the info out a bit by parallel-transporting the initial vectors to other edges in adjacent faces.
254254
Face f = he.face();
255-
BarycentricVector segment = barycentricVectorInFace(he, f);
255+
BarycentricVector segment(he, f);
256256
BarycentricVector segNormal = segment.rotate90(geom);
257257
for (Edge e : f.adjacentEdges()) {
258258
if (e == commonEdge) continue;
259-
BarycentricVector edgeVec = barycentricVectorInFace(e.halfedge(), f);
259+
BarycentricVector edgeVec(e.halfedge(), f);
260260
edgeVec /= edgeVec.norm(geom);
261261
double sinTheta = dot(geom, segment, edgeVec);
262262
double cosTheta = dot(geom, segNormal, edgeVec);
@@ -698,23 +698,6 @@ std::complex<double> SignedHeatSolver::projectedNormal(const SurfacePoint& pA, c
698698
return normal;
699699
}
700700

701-
BarycentricVector SignedHeatSolver::barycentricVectorInFace(const Halfedge& he_, const Face& f) const {
702-
703-
int eIdx = 0;
704-
double sign = 0.;
705-
for (Halfedge he : f.adjacentHalfedges()) {
706-
if (he.edge() == he_.edge()) {
707-
sign = (he.tailVertex() == he_.tailVertex() && he.tipVertex() == he_.tipVertex()) ? 1. : -1.;
708-
break;
709-
}
710-
eIdx++;
711-
}
712-
Vector3 faceCoords = {0, 0, 0};
713-
faceCoords[(eIdx + 1) % 3] = 1;
714-
faceCoords[eIdx] = -1;
715-
return BarycentricVector(f, sign * faceCoords);
716-
}
717-
718701
FaceData<BarycentricVector> SignedHeatSolver::sampleAtFaceBarycenters(const Vector<std::complex<double>>& Xt) {
719702

720703
geom.requireEdgeIndices();
@@ -724,7 +707,7 @@ FaceData<BarycentricVector> SignedHeatSolver::sampleAtFaceBarycenters(const Vect
724707
Vector3 faceCoords = {0, 0, 0};
725708
for (Halfedge he : f.adjacentHalfedges()) {
726709
size_t eIdx = geom.edgeIndices[he.edge()];
727-
BarycentricVector e1 = barycentricVectorInFace(he, f);
710+
BarycentricVector e1(he, f);
728711
if (!he.orientation()) e1 *= -1;
729712
BarycentricVector e2 = e1.rotate90(geom);
730713
e1 /= e1.norm(geom);

0 commit comments

Comments
 (0)