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