Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/sdf.cpp
9903 views
1
// Copyright 2023 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 "hashtable.h"
16
#include "impl.h"
17
#include "manifold/manifold.h"
18
#include "parallel.h"
19
#include "utils.h"
20
#include "vec.h"
21
22
namespace {
23
using namespace manifold;
24
25
constexpr int kCrossing = -2;
26
constexpr int kNone = -1;
27
constexpr ivec4 kVoxelOffset(1, 1, 1, 0);
28
// Maximum fraction of spacing that a vert can move.
29
constexpr double kS = 0.25;
30
// Corresponding approximate distance ratio bound.
31
constexpr double kD = 1 / kS - 1;
32
// Maximum number of opposed verts (of 7) to allow collapse.
33
constexpr int kMaxOpposed = 3;
34
35
ivec3 TetTri0(int i) {
36
constexpr ivec3 tetTri0[16] = {{-1, -1, -1}, //
37
{0, 3, 4}, //
38
{0, 1, 5}, //
39
{1, 5, 3}, //
40
{1, 4, 2}, //
41
{1, 0, 3}, //
42
{2, 5, 0}, //
43
{5, 3, 2}, //
44
{2, 3, 5}, //
45
{0, 5, 2}, //
46
{3, 0, 1}, //
47
{2, 4, 1}, //
48
{3, 5, 1}, //
49
{5, 1, 0}, //
50
{4, 3, 0}, //
51
{-1, -1, -1}};
52
return tetTri0[i];
53
}
54
55
ivec3 TetTri1(int i) {
56
constexpr ivec3 tetTri1[16] = {{-1, -1, -1}, //
57
{-1, -1, -1}, //
58
{-1, -1, -1}, //
59
{3, 4, 1}, //
60
{-1, -1, -1}, //
61
{3, 2, 1}, //
62
{0, 4, 2}, //
63
{-1, -1, -1}, //
64
{-1, -1, -1}, //
65
{2, 4, 0}, //
66
{1, 2, 3}, //
67
{-1, -1, -1}, //
68
{1, 4, 3}, //
69
{-1, -1, -1}, //
70
{-1, -1, -1}, //
71
{-1, -1, -1}};
72
return tetTri1[i];
73
}
74
75
ivec4 Neighbor(ivec4 base, int i) {
76
constexpr ivec4 neighbors[14] = {{0, 0, 0, 1}, //
77
{1, 0, 0, 0}, //
78
{0, 1, 0, 0}, //
79
{0, 0, 1, 0}, //
80
{-1, 0, 0, 1}, //
81
{0, -1, 0, 1}, //
82
{0, 0, -1, 1}, //
83
{-1, -1, -1, 1}, //
84
{-1, 0, 0, 0}, //
85
{0, -1, 0, 0}, //
86
{0, 0, -1, 0}, //
87
{0, -1, -1, 1}, //
88
{-1, 0, -1, 1}, //
89
{-1, -1, 0, 1}};
90
ivec4 neighborIndex = base + neighbors[i];
91
if (neighborIndex.w == 2) {
92
neighborIndex += 1;
93
neighborIndex.w = 0;
94
}
95
return neighborIndex;
96
}
97
98
uint64_t EncodeIndex(ivec4 gridPos, ivec3 gridPow) {
99
return static_cast<uint64_t>(gridPos.w) |
100
static_cast<uint64_t>(gridPos.z) << 1 |
101
static_cast<uint64_t>(gridPos.y) << (1 + gridPow.z) |
102
static_cast<uint64_t>(gridPos.x) << (1 + gridPow.z + gridPow.y);
103
}
104
105
ivec4 DecodeIndex(uint64_t idx, ivec3 gridPow) {
106
ivec4 gridPos;
107
gridPos.w = idx & 1;
108
idx = idx >> 1;
109
gridPos.z = idx & ((1 << gridPow.z) - 1);
110
idx = idx >> gridPow.z;
111
gridPos.y = idx & ((1 << gridPow.y) - 1);
112
idx = idx >> gridPow.y;
113
gridPos.x = idx & ((1 << gridPow.x) - 1);
114
return gridPos;
115
}
116
117
vec3 Position(ivec4 gridIndex, vec3 origin, vec3 spacing) {
118
return origin + spacing * (vec3(gridIndex) + (gridIndex.w == 1 ? 0.0 : -0.5));
119
}
120
121
vec3 Bound(vec3 pos, vec3 origin, vec3 spacing, ivec3 gridSize) {
122
return min(max(pos, origin), origin + spacing * (vec3(gridSize) - 1));
123
}
124
125
double BoundedSDF(ivec4 gridIndex, vec3 origin, vec3 spacing, ivec3 gridSize,
126
double level, std::function<double(vec3)> sdf) {
127
const ivec3 xyz(gridIndex);
128
const int lowerBoundDist = minelem(xyz);
129
const int upperBoundDist = minelem(gridSize - xyz);
130
const int boundDist = std::min(lowerBoundDist, upperBoundDist - gridIndex.w);
131
132
if (boundDist < 0) {
133
return 0.0;
134
}
135
const double d = sdf(Position(gridIndex, origin, spacing)) - level;
136
return boundDist == 0 ? std::min(d, 0.0) : d;
137
}
138
139
// Simplified ITP root finding algorithm - same worst-case performance as
140
// bisection, better average performance.
141
inline vec3 FindSurface(vec3 pos0, double d0, vec3 pos1, double d1, double tol,
142
double level, std::function<double(vec3)> sdf) {
143
if (d0 == 0) {
144
return pos0;
145
} else if (d1 == 0) {
146
return pos1;
147
}
148
149
// Sole tuning parameter, k: (0, 1) - smaller value gets better median
150
// performance, but also hits the worst case more often.
151
const double k = 0.1;
152
const double check = 2 * tol / la::length(pos0 - pos1);
153
double frac = 1;
154
double biFrac = 1;
155
while (frac > check) {
156
const double t = la::lerp(d0 / (d0 - d1), 0.5, k);
157
const double r = biFrac / frac - 0.5;
158
const double x = la::abs(t - 0.5) < r ? t : 0.5 - r * (t < 0.5 ? 1 : -1);
159
160
const vec3 mid = la::lerp(pos0, pos1, x);
161
const double d = sdf(mid) - level;
162
163
if ((d > 0) == (d0 > 0)) {
164
d0 = d;
165
pos0 = mid;
166
frac *= 1 - x;
167
} else {
168
d1 = d;
169
pos1 = mid;
170
frac *= x;
171
}
172
biFrac /= 2;
173
}
174
175
return la::lerp(pos0, pos1, d0 / (d0 - d1));
176
}
177
178
/**
179
* Each GridVert is connected to 14 others, and in charge of 7 of these edges
180
* (see Neighbor() above). Each edge that changes sign contributes one vert,
181
* unless the GridVert is close enough to the surface, in which case it
182
* contributes only a single movedVert and all crossing edgeVerts refer to that.
183
*/
184
struct GridVert {
185
double distance = NAN;
186
int movedVert = kNone;
187
int edgeVerts[7] = {kNone, kNone, kNone, kNone, kNone, kNone, kNone};
188
189
inline bool HasMoved() const { return movedVert >= 0; }
190
191
inline bool SameSide(double dist) const {
192
return (dist > 0) == (distance > 0);
193
}
194
195
inline int Inside() const { return distance > 0 ? 1 : -1; }
196
197
inline int NeighborInside(int i) const {
198
return Inside() * (edgeVerts[i] == kNone ? 1 : -1);
199
}
200
};
201
202
struct NearSurface {
203
VecView<vec3> vertPos;
204
VecView<int> vertIndex;
205
HashTableD<GridVert> gridVerts;
206
VecView<const double> voxels;
207
const std::function<double(vec3)> sdf;
208
const vec3 origin;
209
const ivec3 gridSize;
210
const ivec3 gridPow;
211
const vec3 spacing;
212
const double level;
213
const double tol;
214
215
inline void operator()(uint64_t index) {
216
ZoneScoped;
217
if (gridVerts.Full()) return;
218
219
const ivec4 gridIndex = DecodeIndex(index, gridPow);
220
221
if (la::any(la::greater(ivec3(gridIndex), gridSize))) return;
222
223
GridVert gridVert;
224
gridVert.distance = voxels[EncodeIndex(gridIndex + kVoxelOffset, gridPow)];
225
226
bool keep = false;
227
double vMax = 0;
228
int closestNeighbor = -1;
229
int opposedVerts = 0;
230
for (int i = 0; i < 7; ++i) {
231
const double val =
232
voxels[EncodeIndex(Neighbor(gridIndex, i) + kVoxelOffset, gridPow)];
233
const double valOp = voxels[EncodeIndex(
234
Neighbor(gridIndex, i + 7) + kVoxelOffset, gridPow)];
235
236
if (!gridVert.SameSide(val)) {
237
gridVert.edgeVerts[i] = kCrossing;
238
keep = true;
239
if (!gridVert.SameSide(valOp)) {
240
++opposedVerts;
241
}
242
// Approximate bound on vert movement.
243
if (la::abs(val) > kD * la::abs(gridVert.distance) &&
244
la::abs(val) > la::abs(vMax)) {
245
vMax = val;
246
closestNeighbor = i;
247
}
248
} else if (!gridVert.SameSide(valOp) &&
249
la::abs(valOp) > kD * la::abs(gridVert.distance) &&
250
la::abs(valOp) > la::abs(vMax)) {
251
vMax = valOp;
252
closestNeighbor = i + 7;
253
}
254
}
255
256
// This is where we collapse all the crossing edge verts into this GridVert,
257
// speeding up the algorithm and avoiding poor quality triangles. Without
258
// this step the result is guaranteed 2-manifold, but with this step it can
259
// become an even-manifold with kissing verts. These must be removed in a
260
// post-process: CleanupTopology().
261
if (closestNeighbor >= 0 && opposedVerts <= kMaxOpposed) {
262
const vec3 gridPos = Position(gridIndex, origin, spacing);
263
const ivec4 neighborIndex = Neighbor(gridIndex, closestNeighbor);
264
const vec3 pos = FindSurface(gridPos, gridVert.distance,
265
Position(neighborIndex, origin, spacing),
266
vMax, tol, level, sdf);
267
// Bound the delta of each vert to ensure the tetrahedron cannot invert.
268
if (la::all(la::less(la::abs(pos - gridPos), kS * spacing))) {
269
const int idx = AtomicAdd(vertIndex[0], 1);
270
vertPos[idx] = Bound(pos, origin, spacing, gridSize);
271
gridVert.movedVert = idx;
272
for (int j = 0; j < 7; ++j) {
273
if (gridVert.edgeVerts[j] == kCrossing) gridVert.edgeVerts[j] = idx;
274
}
275
keep = true;
276
}
277
} else {
278
for (int j = 0; j < 7; ++j) gridVert.edgeVerts[j] = kNone;
279
}
280
281
if (keep) gridVerts.Insert(index, gridVert);
282
}
283
};
284
285
struct ComputeVerts {
286
VecView<vec3> vertPos;
287
VecView<int> vertIndex;
288
HashTableD<GridVert> gridVerts;
289
VecView<const double> voxels;
290
const std::function<double(vec3)> sdf;
291
const vec3 origin;
292
const ivec3 gridSize;
293
const ivec3 gridPow;
294
const vec3 spacing;
295
const double level;
296
const double tol;
297
298
void operator()(int idx) {
299
ZoneScoped;
300
uint64_t baseKey = gridVerts.KeyAt(idx);
301
if (baseKey == kOpen) return;
302
303
GridVert& gridVert = gridVerts.At(idx);
304
305
if (gridVert.HasMoved()) return;
306
307
const ivec4 gridIndex = DecodeIndex(baseKey, gridPow);
308
309
const vec3 position = Position(gridIndex, origin, spacing);
310
311
// These seven edges are uniquely owned by this gridVert; any of them
312
// which intersect the surface create a vert.
313
for (int i = 0; i < 7; ++i) {
314
const ivec4 neighborIndex = Neighbor(gridIndex, i);
315
const GridVert& neighbor = gridVerts[EncodeIndex(neighborIndex, gridPow)];
316
317
const double val =
318
std::isfinite(neighbor.distance)
319
? neighbor.distance
320
: voxels[EncodeIndex(neighborIndex + kVoxelOffset, gridPow)];
321
if (gridVert.SameSide(val)) continue;
322
323
if (neighbor.HasMoved()) {
324
gridVert.edgeVerts[i] = neighbor.movedVert;
325
continue;
326
}
327
328
const int idx = AtomicAdd(vertIndex[0], 1);
329
const vec3 pos = FindSurface(position, gridVert.distance,
330
Position(neighborIndex, origin, spacing),
331
val, tol, level, sdf);
332
vertPos[idx] = Bound(pos, origin, spacing, gridSize);
333
gridVert.edgeVerts[i] = idx;
334
}
335
}
336
};
337
338
struct BuildTris {
339
VecView<ivec3> triVerts;
340
VecView<int> triIndex;
341
const HashTableD<GridVert> gridVerts;
342
const ivec3 gridPow;
343
344
void CreateTri(const ivec3& tri, const int edges[6]) {
345
if (tri[0] < 0) return;
346
const ivec3 verts(edges[tri[0]], edges[tri[1]], edges[tri[2]]);
347
if (verts[0] == verts[1] || verts[1] == verts[2] || verts[2] == verts[0])
348
return;
349
int idx = AtomicAdd(triIndex[0], 1);
350
triVerts[idx] = verts;
351
}
352
353
void CreateTris(const ivec4& tet, const int edges[6]) {
354
const int i = (tet[0] > 0 ? 1 : 0) + (tet[1] > 0 ? 2 : 0) +
355
(tet[2] > 0 ? 4 : 0) + (tet[3] > 0 ? 8 : 0);
356
CreateTri(TetTri0(i), edges);
357
CreateTri(TetTri1(i), edges);
358
}
359
360
void operator()(int idx) {
361
ZoneScoped;
362
uint64_t baseKey = gridVerts.KeyAt(idx);
363
if (baseKey == kOpen) return;
364
365
const GridVert& base = gridVerts.At(idx);
366
const ivec4 baseIndex = DecodeIndex(baseKey, gridPow);
367
368
ivec4 leadIndex = baseIndex;
369
if (leadIndex.w == 0)
370
leadIndex.w = 1;
371
else {
372
leadIndex += 1;
373
leadIndex.w = 0;
374
}
375
376
// This GridVert is in charge of the 6 tetrahedra surrounding its edge in
377
// the (1,1,1) direction (edge 0).
378
ivec4 tet(base.NeighborInside(0), base.Inside(), -2, -2);
379
ivec4 thisIndex = baseIndex;
380
thisIndex.x += 1;
381
382
GridVert thisVert = gridVerts[EncodeIndex(thisIndex, gridPow)];
383
384
tet[2] = base.NeighborInside(1);
385
for (const int i : {0, 1, 2}) {
386
thisIndex = leadIndex;
387
--thisIndex[Prev3(i)];
388
// Indices take unsigned input, so check for negatives, given the
389
// decrement. If negative, the vert is outside and only connected to other
390
// outside verts - no edgeVerts.
391
GridVert nextVert = thisIndex[Prev3(i)] < 0
392
? GridVert()
393
: gridVerts[EncodeIndex(thisIndex, gridPow)];
394
tet[3] = base.NeighborInside(Prev3(i) + 4);
395
396
const int edges1[6] = {base.edgeVerts[0],
397
base.edgeVerts[i + 1],
398
nextVert.edgeVerts[Next3(i) + 4],
399
nextVert.edgeVerts[Prev3(i) + 1],
400
thisVert.edgeVerts[i + 4],
401
base.edgeVerts[Prev3(i) + 4]};
402
thisVert = nextVert;
403
CreateTris(tet, edges1);
404
405
thisIndex = baseIndex;
406
++thisIndex[Next3(i)];
407
nextVert = gridVerts[EncodeIndex(thisIndex, gridPow)];
408
tet[2] = tet[3];
409
tet[3] = base.NeighborInside(Next3(i) + 1);
410
411
const int edges2[6] = {base.edgeVerts[0],
412
edges1[5],
413
thisVert.edgeVerts[i + 4],
414
nextVert.edgeVerts[Next3(i) + 4],
415
edges1[3],
416
base.edgeVerts[Next3(i) + 1]};
417
thisVert = nextVert;
418
CreateTris(tet, edges2);
419
420
tet[2] = tet[3];
421
}
422
}
423
};
424
} // namespace
425
426
namespace manifold {
427
428
/**
429
* Constructs a level-set manifold from the input Signed-Distance Function
430
* (SDF). This uses a form of Marching Tetrahedra (akin to Marching
431
* Cubes, but better for manifoldness). Instead of using a cubic grid, it uses a
432
* body-centered cubic grid (two shifted cubic grids). These grid points are
433
* snapped to the surface where possible to keep short edges from forming.
434
*
435
* @param sdf The signed-distance functor, containing this function signature:
436
* `double operator()(vec3 point)`, which returns the
437
* signed distance of a given point in R^3. Positive values are inside,
438
* negative outside. There is no requirement that the function be a true
439
* distance, or even continuous.
440
* @param bounds An axis-aligned box that defines the extent of the grid.
441
* @param edgeLength Approximate maximum edge length of the triangles in the
442
* final result. This affects grid spacing, and hence has a strong effect on
443
* performance.
444
* @param level Extract the surface at this value of your sdf; defaults to
445
* zero. You can inset your mesh by using a positive value, or outset it with a
446
* negative value.
447
* @param tolerance Ensure each vertex is within this distance of the true
448
* surface. Defaults to -1, which will return the interpolated
449
* crossing-point based on the two nearest grid points. Small positive values
450
* will require more sdf evaluations per output vertex.
451
* @param canParallel Parallel policies violate will crash language runtimes
452
* with runtime locks that expect to not be called back by unregistered threads.
453
* This allows bindings use LevelSet despite being compiled with MANIFOLD_PAR
454
* active.
455
*/
456
Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
457
double edgeLength, double level, double tolerance,
458
bool canParallel) {
459
if (tolerance <= 0) {
460
tolerance = std::numeric_limits<double>::infinity();
461
}
462
463
auto pImpl_ = std::make_shared<Impl>();
464
auto& vertPos = pImpl_->vertPos_;
465
466
const vec3 dim = bounds.Size();
467
const ivec3 gridSize(dim / edgeLength + 1.0);
468
const vec3 spacing = dim / (vec3(gridSize - 1));
469
470
const ivec3 gridPow(la::log2(gridSize + 2) + 1);
471
const uint64_t maxIndex = EncodeIndex(ivec4(gridSize + 2, 1), gridPow);
472
473
// Parallel policies violate will crash language runtimes with runtime locks
474
// that expect to not be called back by unregistered threads. This allows
475
// bindings use LevelSet despite being compiled with MANIFOLD_PAR
476
// active.
477
const auto pol = canParallel ? autoPolicy(maxIndex) : ExecutionPolicy::Seq;
478
479
const vec3 origin = bounds.min;
480
Vec<double> voxels(maxIndex);
481
for_each_n(
482
pol, countAt(0_uz), maxIndex,
483
[&voxels, sdf, level, origin, spacing, gridSize, gridPow](uint64_t idx) {
484
voxels[idx] = BoundedSDF(DecodeIndex(idx, gridPow) - kVoxelOffset,
485
origin, spacing, gridSize, level, sdf);
486
});
487
488
size_t tableSize = std::min(
489
2 * maxIndex, static_cast<uint64_t>(10 * la::pow(maxIndex, 0.667)));
490
HashTable<GridVert> gridVerts(tableSize);
491
vertPos.resize_nofill(gridVerts.Size() * 7);
492
493
while (1) {
494
Vec<int> index(1, 0);
495
for_each_n(pol, countAt(0_uz), EncodeIndex(ivec4(gridSize, 1), gridPow),
496
NearSurface({vertPos, index, gridVerts.D(), voxels, sdf, origin,
497
gridSize, gridPow, spacing, level, tolerance}));
498
499
if (gridVerts.Full()) { // Resize HashTable
500
const vec3 lastVert = vertPos[index[0] - 1];
501
const uint64_t lastIndex =
502
EncodeIndex(ivec4(ivec3((lastVert - origin) / spacing), 1), gridPow);
503
const double ratio = static_cast<double>(maxIndex) / lastIndex;
504
505
if (ratio > 1000) // do not trust the ratio if it is too large
506
tableSize *= 2;
507
else
508
tableSize *= ratio;
509
gridVerts = HashTable<GridVert>(tableSize);
510
vertPos = Vec<vec3>(gridVerts.Size() * 7);
511
} else { // Success
512
for_each_n(
513
pol, countAt(0), gridVerts.Size(),
514
ComputeVerts({vertPos, index, gridVerts.D(), voxels, sdf, origin,
515
gridSize, gridPow, spacing, level, tolerance}));
516
vertPos.resize(index[0]);
517
break;
518
}
519
}
520
521
Vec<ivec3> triVerts(gridVerts.Entries() * 12); // worst case
522
523
Vec<int> index(1, 0);
524
for_each_n(pol, countAt(0), gridVerts.Size(),
525
BuildTris({triVerts, index, gridVerts.D(), gridPow}));
526
triVerts.resize(index[0]);
527
528
pImpl_->CreateHalfedges(triVerts);
529
pImpl_->CleanupTopology();
530
pImpl_->RemoveUnreferencedVerts();
531
pImpl_->Finish();
532
pImpl_->InitializeOriginal();
533
pImpl_->MarkCoplanar();
534
return Manifold(pImpl_);
535
}
536
} // namespace manifold
537
538