Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/constructors.cpp
9903 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 "csg_tree.h"
16
#include "disjoint_sets.h"
17
#include "impl.h"
18
#include "manifold/manifold.h"
19
#include "manifold/polygon.h"
20
#include "parallel.h"
21
22
namespace {
23
using namespace manifold;
24
25
template <typename P, typename I>
26
std::shared_ptr<Manifold::Impl> SmoothImpl(
27
const MeshGLP<P, I>& meshGL,
28
const std::vector<Smoothness>& sharpenedEdges) {
29
DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
30
"when supplying tangents, the normal constructor should be used "
31
"rather than Smooth().");
32
33
MeshGLP<P, I> meshTmp = meshGL;
34
meshTmp.faceID.resize(meshGL.NumTri());
35
std::iota(meshTmp.faceID.begin(), meshTmp.faceID.end(), 0);
36
37
std::shared_ptr<Manifold::Impl> impl =
38
std::make_shared<Manifold::Impl>(meshTmp);
39
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
40
// Restore the original faceID
41
const size_t numTri = impl->NumTri();
42
for (size_t i = 0; i < numTri; ++i) {
43
if (meshGL.faceID.size() == numTri) {
44
impl->meshRelation_.triRef[i].faceID =
45
meshGL.faceID[impl->meshRelation_.triRef[i].faceID];
46
} else {
47
impl->meshRelation_.triRef[i].faceID = -1;
48
}
49
}
50
return impl;
51
}
52
} // namespace
53
54
namespace manifold {
55
/**
56
* Constructs a smooth version of the input mesh by creating tangents; this
57
* method will throw if you have supplied tangents with your mesh already. The
58
* actual triangle resolution is unchanged; use the Refine() method to
59
* interpolate to a higher-resolution curve.
60
*
61
* By default, every edge is calculated for maximum smoothness (very much
62
* approximately), attempting to minimize the maximum mean Curvature magnitude.
63
* No higher-order derivatives are considered, as the interpolation is
64
* independent per triangle, only sharing constraints on their boundaries.
65
*
66
* @param meshGL input MeshGL.
67
* @param sharpenedEdges If desired, you can supply a vector of sharpened
68
* halfedges, which should in general be a small subset of all halfedges. Order
69
* of entries doesn't matter, as each one specifies the desired smoothness
70
* (between zero and one, with one the default for all unspecified halfedges)
71
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
72
* between triVert 0 and 1, etc).
73
*
74
* At a smoothness value of zero, a sharp crease is made. The smoothness is
75
* interpolated along each edge, so the specified value should be thought of as
76
* an average. Where exactly two sharpened edges meet at a vertex, their
77
* tangents are rotated to be colinear so that the sharpened edge can be
78
* continuous. Vertices with only one sharpened edge are completely smooth,
79
* allowing sharpened edges to smoothly vanish at termination. A single vertex
80
* can be sharpened by sharping all edges that are incident on it, allowing
81
* cones to be formed.
82
*/
83
Manifold Manifold::Smooth(const MeshGL& meshGL,
84
const std::vector<Smoothness>& sharpenedEdges) {
85
return Manifold(SmoothImpl(meshGL, sharpenedEdges));
86
}
87
88
/**
89
* Constructs a smooth version of the input mesh by creating tangents; this
90
* method will throw if you have supplied tangents with your mesh already. The
91
* actual triangle resolution is unchanged; use the Refine() method to
92
* interpolate to a higher-resolution curve.
93
*
94
* By default, every edge is calculated for maximum smoothness (very much
95
* approximately), attempting to minimize the maximum mean Curvature magnitude.
96
* No higher-order derivatives are considered, as the interpolation is
97
* independent per triangle, only sharing constraints on their boundaries.
98
*
99
* @param meshGL64 input MeshGL64.
100
* @param sharpenedEdges If desired, you can supply a vector of sharpened
101
* halfedges, which should in general be a small subset of all halfedges. Order
102
* of entries doesn't matter, as each one specifies the desired smoothness
103
* (between zero and one, with one the default for all unspecified halfedges)
104
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
105
* between triVert 0 and 1, etc).
106
*
107
* At a smoothness value of zero, a sharp crease is made. The smoothness is
108
* interpolated along each edge, so the specified value should be thought of as
109
* an average. Where exactly two sharpened edges meet at a vertex, their
110
* tangents are rotated to be colinear so that the sharpened edge can be
111
* continuous. Vertices with only one sharpened edge are completely smooth,
112
* allowing sharpened edges to smoothly vanish at termination. A single vertex
113
* can be sharpened by sharping all edges that are incident on it, allowing
114
* cones to be formed.
115
*/
116
Manifold Manifold::Smooth(const MeshGL64& meshGL64,
117
const std::vector<Smoothness>& sharpenedEdges) {
118
return Manifold(SmoothImpl(meshGL64, sharpenedEdges));
119
}
120
121
/**
122
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
123
* and the rest at similarly symmetric points.
124
*/
125
Manifold Manifold::Tetrahedron() {
126
return Manifold(std::make_shared<Impl>(Impl::Shape::Tetrahedron));
127
}
128
129
/**
130
* Constructs a unit cube (edge lengths all one), by default in the first
131
* octant, touching the origin. If any dimensions in size are negative, or if
132
* all are zero, an empty Manifold will be returned.
133
*
134
* @param size The X, Y, and Z dimensions of the box.
135
* @param center Set to true to shift the center to the origin.
136
*/
137
Manifold Manifold::Cube(vec3 size, bool center) {
138
if (size.x < 0.0 || size.y < 0.0 || size.z < 0.0 || la::length(size) == 0.) {
139
return Invalid();
140
}
141
mat3x4 m({{size.x, 0.0, 0.0}, {0.0, size.y, 0.0}, {0.0, 0.0, size.z}},
142
center ? (-size / 2.0) : vec3(0.0));
143
return Manifold(std::make_shared<Impl>(Manifold::Impl::Shape::Cube, m));
144
}
145
146
/**
147
* A convenience constructor for the common case of extruding a circle. Can also
148
* form cones if both radii are specified.
149
*
150
* @param height Z-extent
151
* @param radiusLow Radius of bottom circle. Must be positive.
152
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
153
* radiusLow.
154
* @param circularSegments How many line segments to use around the circle.
155
* Default is calculated by the static Defaults.
156
* @param center Set to true to shift the center to the origin. Default is
157
* origin at the bottom.
158
*/
159
Manifold Manifold::Cylinder(double height, double radiusLow, double radiusHigh,
160
int circularSegments, bool center) {
161
if (height <= 0.0 || radiusLow <= 0.0) {
162
return Invalid();
163
}
164
const double scale = radiusHigh >= 0.0 ? radiusHigh / radiusLow : 1.0;
165
const double radius = fmax(radiusLow, radiusHigh);
166
const int n = circularSegments > 2 ? circularSegments
167
: Quality::GetCircularSegments(radius);
168
169
SimplePolygon circle(n);
170
const double dPhi = 360.0 / n;
171
for (int i = 0; i < n; ++i) {
172
circle[i] = {radiusLow * cosd(dPhi * i), radiusLow * sind(dPhi * i)};
173
}
174
175
Manifold cylinder = Manifold::Extrude({circle}, height, 0, 0.0, vec2(scale));
176
if (center)
177
cylinder = cylinder.Translate(vec3(0.0, 0.0, -height / 2.0)).AsOriginal();
178
return cylinder;
179
}
180
181
/**
182
* Constructs a geodesic sphere of a given radius.
183
*
184
* @param radius Radius of the sphere. Must be positive.
185
* @param circularSegments Number of segments along its
186
* diameter. This number will always be rounded up to the nearest factor of
187
* four, as this sphere is constructed by refining an octahedron. This means
188
* there are a circle of vertices on all three of the axis planes. Default is
189
* calculated by the static Defaults.
190
*/
191
Manifold Manifold::Sphere(double radius, int circularSegments) {
192
if (radius <= 0.0) {
193
return Invalid();
194
}
195
int n = circularSegments > 0 ? (circularSegments + 3) / 4
196
: Quality::GetCircularSegments(radius) / 4;
197
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
198
pImpl_->Subdivide([n](vec3, vec4, vec4) { return n - 1; });
199
for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(),
200
pImpl_->NumVert(), [radius](vec3& v) {
201
v = la::cos(kHalfPi * (1.0 - v));
202
v = radius * la::normalize(v);
203
if (std::isnan(v.x)) v = vec3(0.0);
204
});
205
pImpl_->Finish();
206
// Ignore preceding octahedron.
207
pImpl_->InitializeOriginal();
208
return Manifold(pImpl_);
209
}
210
211
/**
212
* Constructs a manifold from a set of polygons by extruding them along the
213
* Z-axis.
214
* Note that high twistDegrees with small nDivisions may cause
215
* self-intersection. This is not checked here and it is up to the user to
216
* choose the correct parameters.
217
*
218
* @param crossSection A set of non-overlapping polygons to extrude.
219
* @param height Z-extent of extrusion.
220
* @param nDivisions Number of extra copies of the crossSection to insert into
221
* the shape vertically; especially useful in combination with twistDegrees to
222
* avoid interpolation artifacts. Default is none.
223
* @param twistDegrees Amount to twist the top crossSection relative to the
224
* bottom, interpolated linearly for the divisions in between.
225
* @param scaleTop Amount to scale the top (independently in X and Y). If the
226
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
227
* Note that scale is applied after twist.
228
* Default {1, 1}.
229
*/
230
Manifold Manifold::Extrude(const Polygons& crossSection, double height,
231
int nDivisions, double twistDegrees, vec2 scaleTop) {
232
ZoneScoped;
233
if (crossSection.size() == 0 || height <= 0.0) {
234
return Invalid();
235
}
236
237
scaleTop.x = std::max(scaleTop.x, 0.0);
238
scaleTop.y = std::max(scaleTop.y, 0.0);
239
240
auto pImpl_ = std::make_shared<Impl>();
241
++nDivisions;
242
auto& vertPos = pImpl_->vertPos_;
243
Vec<ivec3> triVertsDH;
244
auto& triVerts = triVertsDH;
245
int nCrossSection = 0;
246
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
247
size_t idx = 0;
248
PolygonsIdx polygonsIndexed;
249
for (auto& poly : crossSection) {
250
nCrossSection += poly.size();
251
SimplePolygonIdx simpleIndexed;
252
for (const vec2& polyVert : poly) {
253
vertPos.push_back({polyVert.x, polyVert.y, 0.0});
254
simpleIndexed.push_back({polyVert, static_cast<int>(idx++)});
255
}
256
polygonsIndexed.push_back(simpleIndexed);
257
}
258
for (int i = 1; i < nDivisions + 1; ++i) {
259
double alpha = i / double(nDivisions);
260
double phi = alpha * twistDegrees;
261
vec2 scale = la::lerp(vec2(1.0), scaleTop, alpha);
262
mat2 rotation({cosd(phi), sind(phi)}, {-sind(phi), cosd(phi)});
263
mat2 transform = mat2({scale.x, 0.0}, {0.0, scale.y}) * rotation;
264
size_t j = 0;
265
size_t idx = 0;
266
for (const auto& poly : crossSection) {
267
for (size_t vert = 0; vert < poly.size(); ++vert) {
268
size_t offset = idx + nCrossSection * i;
269
size_t thisVert = vert + offset;
270
size_t lastVert = (vert == 0 ? poly.size() : vert) - 1 + offset;
271
if (i == nDivisions && isCone) {
272
triVerts.push_back(ivec3(nCrossSection * i + j,
273
lastVert - nCrossSection,
274
thisVert - nCrossSection));
275
} else {
276
vec2 pos = transform * poly[vert];
277
vertPos.push_back({pos.x, pos.y, height * alpha});
278
triVerts.push_back(
279
ivec3(thisVert, lastVert, thisVert - nCrossSection));
280
triVerts.push_back(ivec3(lastVert, lastVert - nCrossSection,
281
thisVert - nCrossSection));
282
}
283
}
284
++j;
285
idx += poly.size();
286
}
287
}
288
if (isCone)
289
for (size_t j = 0; j < crossSection.size();
290
++j) // Duplicate vertex for Genus
291
vertPos.push_back({0.0, 0.0, height});
292
std::vector<ivec3> top = TriangulateIdx(polygonsIndexed);
293
for (const ivec3& tri : top) {
294
triVerts.push_back({tri[0], tri[2], tri[1]});
295
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
296
}
297
298
pImpl_->CreateHalfedges(triVertsDH);
299
pImpl_->Finish();
300
pImpl_->InitializeOriginal();
301
pImpl_->MarkCoplanar();
302
return Manifold(pImpl_);
303
}
304
305
/**
306
* Constructs a manifold from a set of polygons by revolving this cross-section
307
* around its Y-axis and then setting this as the Z-axis of the resulting
308
* manifold. If the polygons cross the Y-axis, only the part on the positive X
309
* side is used. Geometrically valid input will result in geometrically valid
310
* output.
311
*
312
* @param crossSection A set of non-overlapping polygons to revolve.
313
* @param circularSegments Number of segments along its diameter. Default is
314
* calculated by the static Defaults.
315
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
316
*/
317
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments,
318
double revolveDegrees) {
319
ZoneScoped;
320
321
Polygons polygons;
322
double radius = 0;
323
for (const SimplePolygon& poly : crossSection) {
324
size_t i = 0;
325
while (i < poly.size() && poly[i].x < 0) {
326
++i;
327
}
328
if (i == poly.size()) {
329
continue;
330
}
331
polygons.push_back({});
332
const size_t start = i;
333
do {
334
if (poly[i].x >= 0) {
335
polygons.back().push_back(poly[i]);
336
radius = std::max(radius, poly[i].x);
337
}
338
const size_t next = i + 1 == poly.size() ? 0 : i + 1;
339
if ((poly[next].x < 0) != (poly[i].x < 0)) {
340
const double y = poly[next].y - poly[next].x *
341
(poly[i].y - poly[next].y) /
342
(poly[i].x - poly[next].x);
343
polygons.back().push_back({0, y});
344
}
345
i = next;
346
} while (i != start);
347
}
348
349
if (polygons.empty()) {
350
return Invalid();
351
}
352
353
if (revolveDegrees > 360.0) {
354
revolveDegrees = 360.0;
355
}
356
const bool isFullRevolution = revolveDegrees == 360.0;
357
358
const int nDivisions =
359
circularSegments > 2
360
? circularSegments
361
: Quality::GetCircularSegments(radius) * revolveDegrees / 360;
362
363
auto pImpl_ = std::make_shared<Impl>();
364
auto& vertPos = pImpl_->vertPos_;
365
Vec<ivec3> triVertsDH;
366
auto& triVerts = triVertsDH;
367
368
std::vector<int> startPoses;
369
std::vector<int> endPoses;
370
371
const double dPhi = revolveDegrees / nDivisions;
372
// first and last slice are distinguished if not a full revolution.
373
const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1;
374
375
for (const auto& poly : polygons) {
376
size_t nPosVerts = 0;
377
size_t nRevolveAxisVerts = 0;
378
for (auto& pt : poly) {
379
if (pt.x > 0) {
380
nPosVerts++;
381
} else {
382
nRevolveAxisVerts++;
383
}
384
}
385
386
for (size_t polyVert = 0; polyVert < poly.size(); ++polyVert) {
387
const size_t startPosIndex = vertPos.size();
388
389
if (!isFullRevolution) startPoses.push_back(startPosIndex);
390
391
const vec2 currPolyVertex = poly[polyVert];
392
const vec2 prevPolyVertex =
393
poly[polyVert == 0 ? poly.size() - 1 : polyVert - 1];
394
395
const int prevStartPosIndex =
396
startPosIndex +
397
(polyVert == 0 ? nRevolveAxisVerts + (nSlices * nPosVerts) : 0) +
398
(prevPolyVertex.x == 0.0 ? -1 : -nSlices);
399
400
for (int slice = 0; slice < nSlices; ++slice) {
401
const double phi = slice * dPhi;
402
if (slice == 0 || currPolyVertex.x > 0) {
403
vertPos.push_back({currPolyVertex.x * cosd(phi),
404
currPolyVertex.x * sind(phi), currPolyVertex.y});
405
}
406
407
if (isFullRevolution || slice > 0) {
408
const int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
409
if (currPolyVertex.x > 0.0) {
410
triVerts.push_back(ivec3(
411
startPosIndex + slice, startPosIndex + lastSlice,
412
// "Reuse" vertex of first slice if it lies on the revolve axis
413
(prevPolyVertex.x == 0.0 ? prevStartPosIndex
414
: prevStartPosIndex + lastSlice)));
415
}
416
417
if (prevPolyVertex.x > 0.0) {
418
triVerts.push_back(
419
ivec3(prevStartPosIndex + lastSlice, prevStartPosIndex + slice,
420
(currPolyVertex.x == 0.0 ? startPosIndex
421
: startPosIndex + slice)));
422
}
423
}
424
}
425
if (!isFullRevolution) endPoses.push_back(vertPos.size() - 1);
426
}
427
}
428
429
// Add front and back triangles if not a full revolution.
430
if (!isFullRevolution) {
431
std::vector<ivec3> frontTriangles = Triangulate(polygons, pImpl_->epsilon_);
432
for (auto& t : frontTriangles) {
433
triVerts.push_back({startPoses[t.x], startPoses[t.y], startPoses[t.z]});
434
}
435
436
for (auto& t : frontTriangles) {
437
triVerts.push_back({endPoses[t.z], endPoses[t.y], endPoses[t.x]});
438
}
439
}
440
441
pImpl_->CreateHalfedges(triVertsDH);
442
pImpl_->Finish();
443
pImpl_->InitializeOriginal();
444
pImpl_->MarkCoplanar();
445
return Manifold(pImpl_);
446
}
447
448
/**
449
* Constructs a new manifold from a vector of other manifolds. This is a purely
450
* topological operation, so care should be taken to avoid creating
451
* overlapping results. It is the inverse operation of Decompose().
452
*
453
* @param manifolds A vector of Manifolds to lazy-union together.
454
*/
455
Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
456
std::vector<std::shared_ptr<CsgLeafNode>> children;
457
for (const auto& manifold : manifolds) {
458
children.push_back(manifold.pNode_->ToLeafNode());
459
}
460
return Manifold(CsgLeafNode::Compose(children));
461
}
462
463
/**
464
* This operation returns a vector of Manifolds that are topologically
465
* disconnected. If everything is connected, the vector is length one,
466
* containing a copy of the original. It is the inverse operation of Compose().
467
*/
468
std::vector<Manifold> Manifold::Decompose() const {
469
ZoneScoped;
470
DisjointSets uf(NumVert());
471
// Graph graph;
472
auto pImpl_ = GetCsgLeafNode().GetImpl();
473
for (const Halfedge& halfedge : pImpl_->halfedge_) {
474
if (halfedge.IsForward()) uf.unite(halfedge.startVert, halfedge.endVert);
475
}
476
std::vector<int> componentIndices;
477
const int numComponents = uf.connectedComponents(componentIndices);
478
479
if (numComponents == 1) {
480
std::vector<Manifold> meshes(1);
481
meshes[0] = *this;
482
return meshes;
483
}
484
Vec<int> vertLabel(componentIndices);
485
486
const int numVert = NumVert();
487
std::vector<Manifold> meshes;
488
for (int i = 0; i < numComponents; ++i) {
489
auto impl = std::make_shared<Impl>();
490
// inherit original object's precision
491
impl->epsilon_ = pImpl_->epsilon_;
492
impl->tolerance_ = pImpl_->tolerance_;
493
494
Vec<int> vertNew2Old(numVert);
495
const int nVert =
496
copy_if(countAt(0), countAt(numVert), vertNew2Old.begin(),
497
[i, &vertLabel](int v) { return vertLabel[v] == i; }) -
498
vertNew2Old.begin();
499
impl->vertPos_.resize(nVert);
500
vertNew2Old.resize(nVert);
501
gather(vertNew2Old.begin(), vertNew2Old.end(), pImpl_->vertPos_.begin(),
502
impl->vertPos_.begin());
503
504
Vec<int> faceNew2Old(NumTri());
505
const auto& halfedge = pImpl_->halfedge_;
506
const int nFace =
507
copy_if(countAt(0_uz), countAt(NumTri()), faceNew2Old.begin(),
508
[i, &vertLabel, &halfedge](int face) {
509
return vertLabel[halfedge[3 * face].startVert] == i;
510
}) -
511
faceNew2Old.begin();
512
513
if (nFace == 0) continue;
514
faceNew2Old.resize(nFace);
515
516
impl->GatherFaces(*pImpl_, faceNew2Old);
517
impl->ReindexVerts(vertNew2Old, pImpl_->NumVert());
518
impl->Finish();
519
520
meshes.push_back(Manifold(impl));
521
}
522
return meshes;
523
}
524
} // namespace manifold
525
526