Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/smoothing.cpp
20849 views
1
// Copyright 2021 The Manifold Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <unordered_map>
16
17
#include "impl.h"
18
#include "parallel.h"
19
20
namespace {
21
using namespace manifold;
22
23
// Returns a normalized vector orthogonal to ref, in the plane of ref and in,
24
// unless in and ref are colinear, in which case it falls back to the plane of
25
// ref and altIn.
26
vec3 OrthogonalTo(vec3 in, vec3 altIn, vec3 ref) {
27
vec3 out = in - la::dot(in, ref) * ref;
28
if (la::dot(out, out) < kPrecision * la::dot(in, in)) {
29
out = altIn - la::dot(altIn, ref) * ref;
30
}
31
return SafeNormalize(out);
32
}
33
34
double Wrap(double radians) {
35
return radians < -kPi ? radians + kTwoPi
36
: radians > kPi ? radians - kTwoPi
37
: radians;
38
}
39
40
// Get the angle between two unit-vectors.
41
double AngleBetween(vec3 a, vec3 b) {
42
const double dot = la::dot(a, b);
43
return dot >= 1 ? 0 : (dot <= -1 ? kPi : la::acos(dot));
44
}
45
46
// Calculate a tangent vector in the form of a weighted cubic Bezier taking as
47
// input the desired tangent direction (length doesn't matter) and the edge
48
// vector to the neighboring vertex. In a symmetric situation where the tangents
49
// at each end are mirror images of each other, this will result in a circular
50
// arc.
51
vec4 CircularTangent(const vec3& tangent, const vec3& edgeVec) {
52
const vec3 dir = SafeNormalize(tangent);
53
54
double weight = std::max(0.5, la::dot(dir, SafeNormalize(edgeVec)));
55
// Quadratic weighted bezier for circular interpolation
56
const vec4 bz2 = vec4(dir * 0.5 * la::length(edgeVec), weight);
57
// Equivalent cubic weighted bezier
58
const vec4 bz3 = la::lerp(vec4(0, 0, 0, 1), bz2, 2 / 3.0);
59
// Convert from homogeneous form to geometric form
60
return vec4(vec3(bz3) / bz3.w, bz3.w);
61
}
62
63
struct InterpTri {
64
VecView<vec3> vertPos;
65
VecView<const Barycentric> vertBary;
66
const Manifold::Impl* impl;
67
68
static vec4 Homogeneous(vec4 v) {
69
v.x *= v.w;
70
v.y *= v.w;
71
v.z *= v.w;
72
return v;
73
}
74
75
static vec4 Homogeneous(vec3 v) { return vec4(v, 1.0); }
76
77
static vec3 HNormalize(vec4 v) {
78
return v.w == 0 ? vec3(v) : (vec3(v) / v.w);
79
}
80
81
static vec4 Scale(vec4 v, double scale) { return vec4(scale * vec3(v), v.w); }
82
83
static vec4 Bezier(vec3 point, vec4 tangent) {
84
return Homogeneous(vec4(point, 0) + tangent);
85
}
86
87
static mat4x2 CubicBezier2Linear(vec4 p0, vec4 p1, vec4 p2, vec4 p3,
88
double x) {
89
mat4x2 out;
90
vec4 p12 = la::lerp(p1, p2, x);
91
out[0] = la::lerp(la::lerp(p0, p1, x), p12, x);
92
out[1] = la::lerp(p12, la::lerp(p2, p3, x), x);
93
return out;
94
}
95
96
static vec3 BezierPoint(mat4x2 points, double x) {
97
return HNormalize(la::lerp(points[0], points[1], x));
98
}
99
100
static vec3 BezierTangent(mat4x2 points) {
101
return SafeNormalize(HNormalize(points[1]) - HNormalize(points[0]));
102
}
103
104
static vec3 RotateFromTo(vec3 v, quat start, quat end) {
105
return la::qrot(end, la::qrot(la::qconj(start), v));
106
}
107
108
static quat Slerp(const quat& x, const quat& y, double a, bool longWay) {
109
quat z = y;
110
double cosTheta = la::dot(x, y);
111
112
// Take the long way around the sphere only when requested
113
if ((cosTheta < 0) != longWay) {
114
z = -y;
115
cosTheta = -cosTheta;
116
}
117
118
if (cosTheta > 1.0 - std::numeric_limits<double>::epsilon()) {
119
return la::lerp(x, z, a); // for numerical stability
120
} else {
121
double angle = std::acos(cosTheta);
122
return (std::sin((1.0 - a) * angle) * x + std::sin(a * angle) * z) /
123
std::sin(angle);
124
}
125
}
126
127
static mat4x2 Bezier2Bezier(const mat3x2& corners, const mat4x2& tangentsX,
128
const mat4x2& tangentsY, double x,
129
const vec3& anchor) {
130
const mat4x2 bez = CubicBezier2Linear(
131
Homogeneous(corners[0]), Bezier(corners[0], tangentsX[0]),
132
Bezier(corners[1], tangentsX[1]), Homogeneous(corners[1]), x);
133
const vec3 end = BezierPoint(bez, x);
134
const vec3 tangent = BezierTangent(bez);
135
136
const mat3x2 nTangentsX(SafeNormalize(vec3(tangentsX[0])),
137
-SafeNormalize(vec3(tangentsX[1])));
138
const mat3x2 biTangents = {
139
OrthogonalTo(vec3(tangentsY[0]), (anchor - corners[0]), nTangentsX[0]),
140
OrthogonalTo(vec3(tangentsY[1]), (anchor - corners[1]), nTangentsX[1])};
141
142
const quat q0 = la::rotation_quat(mat3(
143
nTangentsX[0], biTangents[0], la::cross(nTangentsX[0], biTangents[0])));
144
const quat q1 = la::rotation_quat(mat3(
145
nTangentsX[1], biTangents[1], la::cross(nTangentsX[1], biTangents[1])));
146
const vec3 edge = corners[1] - corners[0];
147
const bool longWay =
148
la::dot(nTangentsX[0], edge) + la::dot(nTangentsX[1], edge) < 0;
149
const quat qTmp = Slerp(q0, q1, x, longWay);
150
const quat q = la::qmul(la::rotation_quat(la::qxdir(qTmp), tangent), qTmp);
151
152
const vec3 delta = la::lerp(RotateFromTo(vec3(tangentsY[0]), q0, q),
153
RotateFromTo(vec3(tangentsY[1]), q1, q), x);
154
const double deltaW = la::lerp(tangentsY[0].w, tangentsY[1].w, x);
155
156
return {Homogeneous(end), vec4(delta, deltaW)};
157
}
158
159
static vec3 Bezier2D(const mat3x4& corners, const mat4& tangentsX,
160
const mat4& tangentsY, double x, double y,
161
const vec3& centroid) {
162
mat4x2 bez0 =
163
Bezier2Bezier({corners[0], corners[1]}, {tangentsX[0], tangentsX[1]},
164
{tangentsY[0], tangentsY[1]}, x, centroid);
165
mat4x2 bez1 =
166
Bezier2Bezier({corners[2], corners[3]}, {tangentsX[2], tangentsX[3]},
167
{tangentsY[2], tangentsY[3]}, 1 - x, centroid);
168
169
const mat4x2 bez =
170
CubicBezier2Linear(bez0[0], Bezier(vec3(bez0[0]), bez0[1]),
171
Bezier(vec3(bez1[0]), bez1[1]), bez1[0], y);
172
return BezierPoint(bez, y);
173
}
174
175
void operator()(const int vert) {
176
vec3& pos = vertPos[vert];
177
const int tri = vertBary[vert].tri;
178
const vec4 uvw = vertBary[vert].uvw;
179
180
const ivec4 halfedges = impl->GetHalfedges(tri);
181
const mat3x4 corners = {
182
impl->vertPos_[impl->halfedge_[halfedges[0]].startVert],
183
impl->vertPos_[impl->halfedge_[halfedges[1]].startVert],
184
impl->vertPos_[impl->halfedge_[halfedges[2]].startVert],
185
halfedges[3] < 0
186
? vec3(0.0)
187
: impl->vertPos_[impl->halfedge_[halfedges[3]].startVert]};
188
189
for (const int i : {0, 1, 2, 3}) {
190
if (uvw[i] == 1) {
191
pos = corners[i];
192
return;
193
}
194
}
195
196
vec4 posH(0.0);
197
198
if (halfedges[3] < 0) { // tri
199
const mat4x3 tangentR = {impl->halfedgeTangent_[halfedges[0]],
200
impl->halfedgeTangent_[halfedges[1]],
201
impl->halfedgeTangent_[halfedges[2]]};
202
const mat4x3 tangentL = {
203
impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge],
204
impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],
205
impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge]};
206
const vec3 centroid = mat3(corners) * vec3(1.0 / 3);
207
208
for (const int i : {0, 1, 2}) {
209
const int j = Next3(i);
210
const int k = Prev3(i);
211
const double x = uvw[k] / (1 - uvw[i]);
212
213
const mat4x2 bez =
214
Bezier2Bezier({corners[j], corners[k]}, {tangentR[j], tangentL[k]},
215
{tangentL[j], tangentR[k]}, x, centroid);
216
217
const mat4x2 bez1 = CubicBezier2Linear(
218
bez[0], Bezier(vec3(bez[0]), bez[1]),
219
Bezier(corners[i], la::lerp(tangentR[i], tangentL[i], x)),
220
Homogeneous(corners[i]), uvw[i]);
221
const vec3 p = BezierPoint(bez1, uvw[i]);
222
posH += Homogeneous(vec4(p, uvw[j] * uvw[k]));
223
}
224
} else { // quad
225
const mat4 tangentsX = {
226
impl->halfedgeTangent_[halfedges[0]],
227
impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],
228
impl->halfedgeTangent_[halfedges[2]],
229
impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge]};
230
const mat4 tangentsY = {
231
impl->halfedgeTangent_[impl->halfedge_[halfedges[3]].pairedHalfedge],
232
impl->halfedgeTangent_[halfedges[1]],
233
impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge],
234
impl->halfedgeTangent_[halfedges[3]]};
235
const vec3 centroid = corners * vec4(0.25);
236
const double x = uvw[1] + uvw[2];
237
const double y = uvw[2] + uvw[3];
238
const vec3 pX = Bezier2D(corners, tangentsX, tangentsY, x, y, centroid);
239
const vec3 pY =
240
Bezier2D({corners[1], corners[2], corners[3], corners[0]},
241
{tangentsY[1], tangentsY[2], tangentsY[3], tangentsY[0]},
242
{tangentsX[1], tangentsX[2], tangentsX[3], tangentsX[0]}, y,
243
1 - x, centroid);
244
posH += Homogeneous(vec4(pX, x * (1 - x)));
245
posH += Homogeneous(vec4(pY, y * (1 - y)));
246
}
247
pos = HNormalize(posH);
248
}
249
};
250
} // namespace
251
252
namespace manifold {
253
254
/**
255
* Get the property normal associated with the startVert of this halfedge, where
256
* normalIdx shows the beginning of where normals are stored in the properties.
257
*/
258
vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const {
259
const int prop = halfedge_[halfedge].propVert;
260
vec3 normal;
261
for (const int i : {0, 1, 2}) {
262
normal[i] = properties_[prop * numProp_ + normalIdx + i];
263
}
264
return normal;
265
}
266
267
/**
268
* Returns a circular tangent for the requested halfedge, orthogonal to the
269
* given normal vector, and avoiding folding.
270
*/
271
vec4 Manifold::Impl::TangentFromNormal(const vec3& normal, int halfedge) const {
272
const Halfedge edge = halfedge_[halfedge];
273
const vec3 edgeVec = vertPos_[edge.endVert] - vertPos_[edge.startVert];
274
const vec3 edgeNormal =
275
faceNormal_[halfedge / 3] + faceNormal_[edge.pairedHalfedge / 3];
276
vec3 dir = la::cross(la::cross(edgeNormal, edgeVec), normal);
277
return CircularTangent(dir, edgeVec);
278
}
279
280
/**
281
* Returns true if this halfedge should be marked as the interior of a quad, as
282
* defined by its two triangles referring to the same face, and those triangles
283
* having no further face neighbors beyond.
284
*/
285
bool Manifold::Impl::IsInsideQuad(int halfedge) const {
286
if (halfedgeTangent_.size() > 0) {
287
return halfedgeTangent_[halfedge].w < 0;
288
}
289
const int tri = halfedge / 3;
290
const TriRef ref = meshRelation_.triRef[tri];
291
const int pair = halfedge_[halfedge].pairedHalfedge;
292
const int pairTri = pair / 3;
293
const TriRef pairRef = meshRelation_.triRef[pairTri];
294
if (!ref.SameFace(pairRef)) return false;
295
296
auto SameFace = [this](int halfedge, const TriRef& ref) {
297
return ref.SameFace(
298
meshRelation_.triRef[halfedge_[halfedge].pairedHalfedge / 3]);
299
};
300
301
int neighbor = NextHalfedge(halfedge);
302
if (SameFace(neighbor, ref)) return false;
303
neighbor = NextHalfedge(neighbor);
304
if (SameFace(neighbor, ref)) return false;
305
neighbor = NextHalfedge(pair);
306
if (SameFace(neighbor, pairRef)) return false;
307
neighbor = NextHalfedge(neighbor);
308
if (SameFace(neighbor, pairRef)) return false;
309
return true;
310
}
311
312
/**
313
* Returns true if this halfedge is an interior of a quad, as defined by its
314
* halfedge tangent having negative weight.
315
*/
316
bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const {
317
return halfedgeTangent_.size() > 0 && halfedgeTangent_[halfedge].w < 0;
318
}
319
320
// sharpenedEdges are referenced to the input Mesh, but the triangles have
321
// been sorted in creating the Manifold, so the indices are converted using
322
// meshRelation_.faceID, which temporarily holds the mapping.
323
std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges(
324
const std::vector<Smoothness>& sharpenedEdges) const {
325
std::unordered_map<int, int> oldHalfedge2New;
326
for (size_t tri = 0; tri < NumTri(); ++tri) {
327
int oldTri = meshRelation_.triRef[tri].faceID;
328
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
329
}
330
std::vector<Smoothness> newSharp = sharpenedEdges;
331
for (Smoothness& edge : newSharp) {
332
edge.halfedge = oldHalfedge2New[edge.halfedge];
333
}
334
return newSharp;
335
}
336
337
// Find faces containing at least 3 triangles - these will not have
338
// interpolated normals - all their vert normals must match their face normal.
339
Vec<bool> Manifold::Impl::FlatFaces() const {
340
const int numTri = NumTri();
341
Vec<bool> triIsFlatFace(numTri, false);
342
for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,
343
[this, &triIsFlatFace](const int tri) {
344
const TriRef& ref = meshRelation_.triRef[tri];
345
int faceNeighbors = 0;
346
ivec3 faceTris = {-1, -1, -1};
347
for (const int j : {0, 1, 2}) {
348
const int neighborTri =
349
halfedge_[3 * tri + j].pairedHalfedge / 3;
350
const TriRef& jRef = meshRelation_.triRef[neighborTri];
351
if (jRef.SameFace(ref)) {
352
++faceNeighbors;
353
faceTris[j] = neighborTri;
354
}
355
}
356
if (faceNeighbors > 1) {
357
triIsFlatFace[tri] = true;
358
for (const int j : {0, 1, 2}) {
359
if (faceTris[j] >= 0) {
360
triIsFlatFace[faceTris[j]] = true;
361
}
362
}
363
}
364
});
365
return triIsFlatFace;
366
}
367
368
// Returns a vector of length numVert that has a tri that is part of a
369
// neighboring flat face if there is only one flat face. If there are none it
370
// gets -1, and if there are more than one it gets -2.
371
Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const {
372
Vec<int> vertFlatFace(NumVert(), -1);
373
Vec<TriRef> vertRef(NumVert(), {-1, -1, -1, -1});
374
for (size_t tri = 0; tri < NumTri(); ++tri) {
375
if (flatFaces[tri]) {
376
for (const int j : {0, 1, 2}) {
377
const int vert = halfedge_[3 * tri + j].startVert;
378
if (vertRef[vert].SameFace(meshRelation_.triRef[tri])) continue;
379
vertRef[vert] = meshRelation_.triRef[tri];
380
vertFlatFace[vert] = vertFlatFace[vert] == -1 ? tri : -2;
381
}
382
}
383
}
384
return vertFlatFace;
385
}
386
387
Vec<int> Manifold::Impl::VertHalfedge() const {
388
Vec<int> vertHalfedge(NumVert());
389
Vec<uint8_t> counters(NumVert(), 0);
390
for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),
391
[&vertHalfedge, &counters, this](const int idx) {
392
auto old = std::atomic_exchange(
393
reinterpret_cast<std::atomic<uint8_t>*>(
394
&counters[halfedge_[idx].startVert]),
395
static_cast<uint8_t>(1));
396
if (old == 1) return;
397
// arbitrary, last one wins.
398
vertHalfedge[halfedge_[idx].startVert] = idx;
399
});
400
return vertHalfedge;
401
}
402
403
std::vector<Smoothness> Manifold::Impl::SharpenEdges(
404
double minSharpAngle, double minSmoothness) const {
405
std::vector<Smoothness> sharpenedEdges;
406
const double minRadians = radians(minSharpAngle);
407
for (size_t e = 0; e < halfedge_.size(); ++e) {
408
if (!halfedge_[e].IsForward()) continue;
409
const size_t pair = halfedge_[e].pairedHalfedge;
410
const double dihedral =
411
std::acos(la::dot(faceNormal_[e / 3], faceNormal_[pair / 3]));
412
if (dihedral > minRadians) {
413
sharpenedEdges.push_back({e, minSmoothness});
414
sharpenedEdges.push_back({pair, minSmoothness});
415
}
416
}
417
return sharpenedEdges;
418
}
419
420
/**
421
* Sharpen tangents that intersect an edge to sharpen that edge. The weight is
422
* unchanged, as this has a squared effect on radius of curvature, except
423
* in the case of zero radius, which is marked with weight = 0.
424
*/
425
void Manifold::Impl::SharpenTangent(int halfedge, double smoothness) {
426
halfedgeTangent_[halfedge] =
427
vec4(smoothness * vec3(halfedgeTangent_[halfedge]),
428
smoothness == 0 ? 0 : halfedgeTangent_[halfedge].w);
429
}
430
431
/**
432
* Instead of calculating the internal shared normals like CalculateNormals
433
* does, this method fills in vertex properties, unshared across edges that
434
* are bent more than minSharpAngle.
435
*/
436
void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {
437
if (IsEmpty()) return;
438
if (normalIdx < 0) return;
439
440
const int oldNumProp = NumProp();
441
442
Vec<bool> triIsFlatFace = FlatFaces();
443
Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);
444
Vec<int> vertNumSharp(NumVert(), 0);
445
for (size_t e = 0; e < halfedge_.size(); ++e) {
446
if (!halfedge_[e].IsForward()) continue;
447
const int pair = halfedge_[e].pairedHalfedge;
448
const int tri1 = e / 3;
449
const int tri2 = pair / 3;
450
const double dihedral =
451
degrees(std::acos(la::dot(faceNormal_[tri1], faceNormal_[tri2])));
452
if (dihedral > minSharpAngle) {
453
++vertNumSharp[halfedge_[e].startVert];
454
++vertNumSharp[halfedge_[e].endVert];
455
} else {
456
const bool faceSplit =
457
triIsFlatFace[tri1] != triIsFlatFace[tri2] ||
458
(triIsFlatFace[tri1] && triIsFlatFace[tri2] &&
459
!meshRelation_.triRef[tri1].SameFace(meshRelation_.triRef[tri2]));
460
if (vertFlatFace[halfedge_[e].startVert] == -2 && faceSplit) {
461
++vertNumSharp[halfedge_[e].startVert];
462
}
463
if (vertFlatFace[halfedge_[e].endVert] == -2 && faceSplit) {
464
++vertNumSharp[halfedge_[e].endVert];
465
}
466
}
467
}
468
469
const int numProp = std::max(oldNumProp, normalIdx + 3);
470
Vec<double> oldProperties(numProp * NumPropVert(), 0);
471
properties_.swap(oldProperties);
472
numProp_ = numProp;
473
474
Vec<int> oldHalfedgeProp(halfedge_.size());
475
for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),
476
[this, &oldHalfedgeProp](int i) {
477
oldHalfedgeProp[i] = halfedge_[i].propVert;
478
halfedge_[i].propVert = -1;
479
});
480
481
const int numEdge = halfedge_.size();
482
for (int startEdge = 0; startEdge < numEdge; ++startEdge) {
483
if (halfedge_[startEdge].propVert >= 0) continue;
484
const int vert = halfedge_[startEdge].startVert;
485
486
if (vertNumSharp[vert] < 2) { // vertex has single normal
487
const vec3 normal = vertFlatFace[vert] >= 0
488
? faceNormal_[vertFlatFace[vert]]
489
: vertNormal_[vert];
490
int lastProp = -1;
491
ForVert(startEdge, [&](int current) {
492
const int prop = oldHalfedgeProp[current];
493
halfedge_[current].propVert = prop;
494
if (prop == lastProp) return;
495
lastProp = prop;
496
// update property vertex
497
auto start = oldProperties.begin() + prop * oldNumProp;
498
std::copy(start, start + oldNumProp,
499
properties_.begin() + prop * numProp);
500
for (const int i : {0, 1, 2})
501
properties_[prop * numProp + normalIdx + i] = normal[i];
502
});
503
} else { // vertex has multiple normals
504
const vec3 centerPos = vertPos_[vert];
505
// Length degree
506
std::vector<int> group;
507
// Length number of normals
508
std::vector<vec3> normals;
509
int current = startEdge;
510
int prevFace = current / 3;
511
512
do { // find a sharp edge to start on
513
int next = NextHalfedge(halfedge_[current].pairedHalfedge);
514
const int face = next / 3;
515
516
const double dihedral = degrees(
517
std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace])));
518
if (dihedral > minSharpAngle ||
519
triIsFlatFace[face] != triIsFlatFace[prevFace] ||
520
(triIsFlatFace[face] && triIsFlatFace[prevFace] &&
521
!meshRelation_.triRef[face].SameFace(
522
meshRelation_.triRef[prevFace]))) {
523
break;
524
}
525
current = next;
526
prevFace = face;
527
} while (current != startEdge);
528
529
const int endEdge = current;
530
531
struct FaceEdge {
532
int face;
533
vec3 edgeVec;
534
};
535
536
// calculate pseudo-normals between each sharp edge
537
ForVert<FaceEdge>(
538
endEdge,
539
[this, centerPos, &vertNumSharp, &vertFlatFace](int current) {
540
if (IsInsideQuad(current)) {
541
return FaceEdge({current / 3, vec3(NAN)});
542
}
543
const int vert = halfedge_[current].endVert;
544
vec3 pos = vertPos_[vert];
545
if (vertNumSharp[vert] < 2) {
546
// opposite vert has fixed normal
547
const vec3 normal = vertFlatFace[vert] >= 0
548
? faceNormal_[vertFlatFace[vert]]
549
: vertNormal_[vert];
550
// Flair out the normal we're calculating to give the edge a
551
// more constant curvature to meet the opposite normal. Achieve
552
// this by pointing the tangent toward the opposite bezier
553
// control point instead of the vert itself.
554
pos += vec3(
555
TangentFromNormal(normal, halfedge_[current].pairedHalfedge));
556
}
557
return FaceEdge({current / 3, SafeNormalize(pos - centerPos)});
558
},
559
[this, &triIsFlatFace, &normals, &group, minSharpAngle](
560
int, const FaceEdge& here, FaceEdge& next) {
561
const double dihedral = degrees(std::acos(
562
la::dot(faceNormal_[here.face], faceNormal_[next.face])));
563
if (dihedral > minSharpAngle ||
564
triIsFlatFace[here.face] != triIsFlatFace[next.face] ||
565
(triIsFlatFace[here.face] && triIsFlatFace[next.face] &&
566
!meshRelation_.triRef[here.face].SameFace(
567
meshRelation_.triRef[next.face]))) {
568
normals.push_back(vec3(0.0));
569
}
570
group.push_back(normals.size() - 1);
571
if (std::isfinite(next.edgeVec.x)) {
572
normals.back() +=
573
SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) *
574
AngleBetween(here.edgeVec, next.edgeVec);
575
} else {
576
next.edgeVec = here.edgeVec;
577
}
578
});
579
580
for (auto& normal : normals) {
581
normal = SafeNormalize(normal);
582
}
583
584
int lastGroup = 0;
585
int lastProp = -1;
586
int newProp = -1;
587
int idx = 0;
588
ForVert(endEdge, [&](int current1) {
589
const int prop = oldHalfedgeProp[current1];
590
auto start = oldProperties.begin() + prop * oldNumProp;
591
592
if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) {
593
// split property vertex, duplicating but with an updated normal
594
lastGroup = group[idx];
595
newProp = NumPropVert();
596
properties_.resize(properties_.size() + numProp);
597
std::copy(start, start + oldNumProp,
598
properties_.begin() + newProp * numProp);
599
for (const int i : {0, 1, 2}) {
600
properties_[newProp * numProp + normalIdx + i] =
601
normals[group[idx]][i];
602
}
603
} else if (prop != lastProp) {
604
// update property vertex
605
lastProp = prop;
606
newProp = prop;
607
std::copy(start, start + oldNumProp,
608
properties_.begin() + prop * numProp);
609
for (const int i : {0, 1, 2})
610
properties_[prop * numProp + normalIdx + i] =
611
normals[group[idx]][i];
612
}
613
614
// point to updated property vertex
615
halfedge_[current1].propVert = newProp;
616
++idx;
617
});
618
}
619
}
620
}
621
622
/**
623
* Tangents get flattened to create sharp edges by setting their weight to zero.
624
* This is the natural limit of reducing the weight to increase the sharpness
625
* smoothly. This limit gives a decent shape, but it causes the parameterization
626
* to be stretched and compresses it near the edges, which is good for resolving
627
* tight curvature, but bad for property interpolation. This function fixes the
628
* parameter stretch at the limit for sharp edges, since there is no curvature
629
* to resolve. Note this also changes the overall shape - making it more evenly
630
* curved.
631
*/
632
void Manifold::Impl::LinearizeFlatTangents() {
633
const int n = halfedgeTangent_.size();
634
for_each_n(autoPolicy(n, 1e4), countAt(0), n, [this](const int halfedge) {
635
vec4& tangent = halfedgeTangent_[halfedge];
636
vec4& otherTangent = halfedgeTangent_[halfedge_[halfedge].pairedHalfedge];
637
638
const bool flat[2] = {tangent.w == 0, otherTangent.w == 0};
639
if (!halfedge_[halfedge].IsForward() || (!flat[0] && !flat[1])) {
640
return;
641
}
642
643
const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -
644
vertPos_[halfedge_[halfedge].startVert];
645
646
if (flat[0] && flat[1]) {
647
tangent = vec4(edgeVec / 3.0, 1);
648
otherTangent = vec4(-edgeVec / 3.0, 1);
649
} else if (flat[0]) {
650
tangent = vec4((edgeVec + vec3(otherTangent)) / 2.0, 1);
651
} else {
652
otherTangent = vec4((-edgeVec + vec3(tangent)) / 2.0, 1);
653
}
654
});
655
}
656
657
/**
658
* Redistribute the tangents around each vertex so that the angles between them
659
* have the same ratios as the angles of the triangles between the corresponding
660
* edges. This avoids folding the output shape and gives smoother results. There
661
* must be at least one fixed halfedge on a vertex for that vertex to be
662
* operated on. If there is only one, then that halfedge is not treated as
663
* fixed, but the whole circle is turned to an average orientation.
664
*/
665
void Manifold::Impl::DistributeTangents(const Vec<bool>& fixedHalfedges) {
666
const int numHalfedge = fixedHalfedges.size();
667
for_each_n(
668
autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,
669
[this, &fixedHalfedges](int halfedge) {
670
if (!fixedHalfedges[halfedge]) return;
671
672
if (IsMarkedInsideQuad(halfedge)) {
673
halfedge = NextHalfedge(halfedge_[halfedge].pairedHalfedge);
674
}
675
676
vec3 normal(0.0);
677
Vec<double> currentAngle;
678
Vec<double> desiredAngle;
679
680
const vec3 approxNormal = vertNormal_[halfedge_[halfedge].startVert];
681
const vec3 center = vertPos_[halfedge_[halfedge].startVert];
682
vec3 lastEdgeVec =
683
SafeNormalize(vertPos_[halfedge_[halfedge].endVert] - center);
684
const vec3 firstTangent =
685
SafeNormalize(vec3(halfedgeTangent_[halfedge]));
686
vec3 lastTangent = firstTangent;
687
int current = halfedge;
688
do {
689
current = NextHalfedge(halfedge_[current].pairedHalfedge);
690
if (IsMarkedInsideQuad(current)) continue;
691
const vec3 thisEdgeVec =
692
SafeNormalize(vertPos_[halfedge_[current].endVert] - center);
693
const vec3 thisTangent =
694
SafeNormalize(vec3(halfedgeTangent_[current]));
695
normal += la::cross(thisTangent, lastTangent);
696
// cumulative sum
697
desiredAngle.push_back(
698
AngleBetween(thisEdgeVec, lastEdgeVec) +
699
(desiredAngle.size() > 0 ? desiredAngle.back() : 0));
700
if (current == halfedge) {
701
currentAngle.push_back(kTwoPi);
702
} else {
703
currentAngle.push_back(AngleBetween(thisTangent, firstTangent));
704
if (la::dot(approxNormal, la::cross(thisTangent, firstTangent)) <
705
0) {
706
currentAngle.back() = kTwoPi - currentAngle.back();
707
}
708
}
709
lastEdgeVec = thisEdgeVec;
710
lastTangent = thisTangent;
711
} while (!fixedHalfedges[current]);
712
713
if (currentAngle.size() == 1 || la::dot(normal, normal) == 0) return;
714
715
const double scale = currentAngle.back() / desiredAngle.back();
716
double offset = 0;
717
if (current == halfedge) { // only one - find average offset
718
for (size_t i = 0; i < currentAngle.size(); ++i) {
719
offset += Wrap(currentAngle[i] - scale * desiredAngle[i]);
720
}
721
offset /= currentAngle.size();
722
}
723
724
current = halfedge;
725
size_t i = 0;
726
do {
727
current = NextHalfedge(halfedge_[current].pairedHalfedge);
728
if (IsMarkedInsideQuad(current)) continue;
729
desiredAngle[i] *= scale;
730
const double lastAngle = i > 0 ? desiredAngle[i - 1] : 0;
731
// shrink obtuse angles
732
if (desiredAngle[i] - lastAngle > kPi) {
733
desiredAngle[i] = lastAngle + kPi;
734
} else if (i + 1 < desiredAngle.size() &&
735
scale * desiredAngle[i + 1] - desiredAngle[i] > kPi) {
736
desiredAngle[i] = scale * desiredAngle[i + 1] - kPi;
737
}
738
const double angle = currentAngle[i] - desiredAngle[i] - offset;
739
vec3 tangent(halfedgeTangent_[current]);
740
const quat q = la::rotation_quat(la::normalize(normal), angle);
741
halfedgeTangent_[current] =
742
vec4(la::qrot(q, tangent), halfedgeTangent_[current].w);
743
++i;
744
} while (!fixedHalfedges[current]);
745
});
746
}
747
748
/**
749
* Calculates halfedgeTangent_, allowing the manifold to be refined and
750
* smoothed. The tangents form weighted cubic Beziers along each edge. This
751
* function creates circular arcs where possible (minimizing maximum curvature),
752
* constrained to the indicated property normals. Across edges that form
753
* discontinuities in the normals, the tangent vectors are zero-length, allowing
754
* the shape to form a sharp corner with minimal oscillation.
755
*/
756
void Manifold::Impl::CreateTangents(int normalIdx) {
757
ZoneScoped;
758
const int numVert = NumVert();
759
const int numHalfedge = halfedge_.size();
760
halfedgeTangent_.clear();
761
Vec<vec4> tangent(numHalfedge);
762
Vec<bool> fixedHalfedge(numHalfedge, false);
763
764
Vec<int> vertHalfedge = VertHalfedge();
765
for_each_n(
766
autoPolicy(numVert, 1e4), vertHalfedge.begin(), numVert,
767
[this, &tangent, &fixedHalfedge, normalIdx](int e) {
768
struct FlatNormal {
769
bool isFlatFace;
770
vec3 normal;
771
};
772
773
ivec2 faceEdges(-1, -1);
774
775
ForVert<FlatNormal>(
776
e,
777
[normalIdx, this](int halfedge) {
778
const vec3 normal = GetNormal(halfedge, normalIdx);
779
const vec3 diff = faceNormal_[halfedge / 3] - normal;
780
return FlatNormal(
781
{la::dot(diff, diff) < kPrecision * kPrecision, normal});
782
},
783
[&faceEdges, &tangent, &fixedHalfedge, this](
784
int halfedge, const FlatNormal& here, const FlatNormal& next) {
785
if (IsInsideQuad(halfedge)) {
786
tangent[halfedge] = {0, 0, 0, -1};
787
return;
788
}
789
// mark special edges
790
const vec3 diff = next.normal - here.normal;
791
const bool differentNormals =
792
la::dot(diff, diff) > kPrecision * kPrecision;
793
if (differentNormals || here.isFlatFace != next.isFlatFace) {
794
fixedHalfedge[halfedge] = true;
795
if (faceEdges[0] == -1) {
796
faceEdges[0] = halfedge;
797
} else if (faceEdges[1] == -1) {
798
faceEdges[1] = halfedge;
799
} else {
800
faceEdges[0] = -2;
801
}
802
}
803
// calculate tangents
804
if (differentNormals) {
805
const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -
806
vertPos_[halfedge_[halfedge].startVert];
807
const vec3 dir = la::cross(here.normal, next.normal);
808
tangent[halfedge] = CircularTangent(
809
(la::dot(dir, edgeVec) < 0 ? -1.0 : 1.0) * dir, edgeVec);
810
} else {
811
tangent[halfedge] = TangentFromNormal(here.normal, halfedge);
812
}
813
});
814
815
if (faceEdges[0] >= 0 && faceEdges[1] >= 0) {
816
const vec3 edge0 = vertPos_[halfedge_[faceEdges[0]].endVert] -
817
vertPos_[halfedge_[faceEdges[0]].startVert];
818
const vec3 edge1 = vertPos_[halfedge_[faceEdges[1]].endVert] -
819
vertPos_[halfedge_[faceEdges[1]].startVert];
820
const vec3 newTangent = la::normalize(edge0) - la::normalize(edge1);
821
tangent[faceEdges[0]] = CircularTangent(newTangent, edge0);
822
tangent[faceEdges[1]] = CircularTangent(-newTangent, edge1);
823
} else if (faceEdges[0] == -1 && faceEdges[0] == -1) {
824
fixedHalfedge[e] = true;
825
}
826
});
827
828
halfedgeTangent_.swap(tangent);
829
DistributeTangents(fixedHalfedge);
830
}
831
832
/**
833
* Calculates halfedgeTangent_, allowing the manifold to be refined and
834
* smoothed. The tangents form weighted cubic Beziers along each edge. This
835
* function creates circular arcs where possible (minimizing maximum curvature),
836
* constrained to the vertex normals. Where sharpenedEdges are specified, the
837
* tangents are shortened that intersect the sharpened edge, concentrating the
838
* curvature there, while the tangents of the sharp edges themselves are aligned
839
* for continuity.
840
*/
841
void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {
842
ZoneScoped;
843
const int numHalfedge = halfedge_.size();
844
halfedgeTangent_.clear();
845
Vec<vec4> tangent(numHalfedge);
846
Vec<bool> fixedHalfedge(numHalfedge, false);
847
848
Vec<int> vertHalfedge = VertHalfedge();
849
Vec<bool> triIsFlatFace = FlatFaces();
850
Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);
851
Vec<vec3> vertNormal = vertNormal_;
852
for (size_t v = 0; v < NumVert(); ++v) {
853
if (vertFlatFace[v] >= 0) {
854
vertNormal[v] = faceNormal_[vertFlatFace[v]];
855
}
856
}
857
858
for_each_n(autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,
859
[&tangent, &vertNormal, this](const int edgeIdx) {
860
tangent[edgeIdx] =
861
IsInsideQuad(edgeIdx)
862
? vec4(0, 0, 0, -1)
863
: TangentFromNormal(
864
vertNormal[halfedge_[edgeIdx].startVert], edgeIdx);
865
});
866
867
halfedgeTangent_.swap(tangent);
868
869
// Add sharpened edges around faces, just on the face side.
870
for (size_t tri = 0; tri < NumTri(); ++tri) {
871
if (!triIsFlatFace[tri]) continue;
872
for (const int j : {0, 1, 2}) {
873
const int tri2 = halfedge_[3 * tri + j].pairedHalfedge / 3;
874
if (!triIsFlatFace[tri2] ||
875
!meshRelation_.triRef[tri].SameFace(meshRelation_.triRef[tri2])) {
876
sharpenedEdges.push_back({3 * tri + j, 0});
877
}
878
}
879
}
880
881
using Pair = std::pair<Smoothness, Smoothness>;
882
// Fill in missing pairs with default smoothness = 1.
883
std::map<int, Pair> edges;
884
for (Smoothness edge : sharpenedEdges) {
885
if (edge.smoothness >= 1) continue;
886
const bool forward = halfedge_[edge.halfedge].IsForward();
887
const int pair = halfedge_[edge.halfedge].pairedHalfedge;
888
const int idx = forward ? edge.halfedge : pair;
889
if (edges.find(idx) == edges.end()) {
890
edges[idx] = {edge, {static_cast<size_t>(pair), 1}};
891
if (!forward) std::swap(edges[idx].first, edges[idx].second);
892
} else {
893
Smoothness& e = forward ? edges[idx].first : edges[idx].second;
894
e.smoothness = std::min(edge.smoothness, e.smoothness);
895
}
896
}
897
898
std::map<int, std::vector<Pair>> vertTangents;
899
for (const auto& value : edges) {
900
const Pair edge = value.second;
901
vertTangents[halfedge_[edge.first.halfedge].startVert].push_back(edge);
902
vertTangents[halfedge_[edge.second.halfedge].startVert].push_back(
903
{edge.second, edge.first});
904
}
905
906
const int numVert = NumVert();
907
for_each_n(
908
autoPolicy(numVert, 1e4), countAt(0), numVert,
909
[this, &vertTangents, &fixedHalfedge, &vertHalfedge,
910
&triIsFlatFace](int v) {
911
auto it = vertTangents.find(v);
912
if (it == vertTangents.end()) {
913
fixedHalfedge[vertHalfedge[v]] = true;
914
return;
915
}
916
const std::vector<Pair>& vert = it->second;
917
// Sharp edges that end are smooth at their terminal vert.
918
if (vert.size() == 1) return;
919
if (vert.size() == 2) { // Make continuous edge
920
const int first = vert[0].first.halfedge;
921
const int second = vert[1].first.halfedge;
922
fixedHalfedge[first] = true;
923
fixedHalfedge[second] = true;
924
const vec3 newTangent = la::normalize(vec3(halfedgeTangent_[first]) -
925
vec3(halfedgeTangent_[second]));
926
927
const vec3 pos = vertPos_[halfedge_[first].startVert];
928
halfedgeTangent_[first] = CircularTangent(
929
newTangent, vertPos_[halfedge_[first].endVert] - pos);
930
halfedgeTangent_[second] = CircularTangent(
931
-newTangent, vertPos_[halfedge_[second].endVert] - pos);
932
933
double smoothness =
934
(vert[0].second.smoothness + vert[1].first.smoothness) / 2;
935
ForVert(first, [this, &smoothness, &vert, first,
936
second](int current) {
937
if (current == second) {
938
smoothness =
939
(vert[1].second.smoothness + vert[0].first.smoothness) / 2;
940
} else if (current != first && !IsMarkedInsideQuad(current)) {
941
SharpenTangent(current, smoothness);
942
}
943
});
944
} else { // Sharpen vertex uniformly
945
double smoothness = 0;
946
double denom = 0;
947
for (const Pair& pair : vert) {
948
smoothness += pair.first.smoothness;
949
smoothness += pair.second.smoothness;
950
denom += pair.first.smoothness == 0 ? 0 : 1;
951
denom += pair.second.smoothness == 0 ? 0 : 1;
952
}
953
smoothness /= denom;
954
955
ForVert(vert[0].first.halfedge,
956
[this, &triIsFlatFace, smoothness](int current) {
957
if (!IsMarkedInsideQuad(current)) {
958
const int pair = halfedge_[current].pairedHalfedge;
959
SharpenTangent(current, triIsFlatFace[current / 3] ||
960
triIsFlatFace[pair / 3]
961
? 0
962
: smoothness);
963
}
964
});
965
}
966
});
967
968
LinearizeFlatTangents();
969
DistributeTangents(fixedHalfedge);
970
}
971
972
void Manifold::Impl::Refine(std::function<int(vec3, vec4, vec4)> edgeDivisions,
973
bool keepInterior) {
974
if (IsEmpty()) return;
975
Manifold::Impl old = *this;
976
Vec<Barycentric> vertBary = Subdivide(edgeDivisions, keepInterior);
977
if (vertBary.size() == 0) return;
978
979
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
980
for_each_n(autoPolicy(NumTri(), 1e4), countAt(0), NumVert(),
981
InterpTri({vertPos_, vertBary, &old}));
982
}
983
984
halfedgeTangent_.clear();
985
Finish();
986
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
987
MarkCoplanar();
988
}
989
meshRelation_.originalID = -1;
990
}
991
992
} // namespace manifold
993
994