1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <map>
#include <numeric>
#include "./boolean3.h"
#include "./csg_tree.h"
#include "./impl.h"
#include "./parallel.h"
namespace {
using namespace manifold;
ExecutionParams manifoldParams;
struct UpdateProperties {
double* properties;
const int numProp;
const double* oldProperties;
const int numOldProp;
const vec3* vertPos;
const ivec3* triProperties;
const Halfedge* halfedges;
std::function<void(double*, vec3, const double*)> propFunc;
void operator()(int tri) {
for (int i : {0, 1, 2}) {
const int vert = halfedges[3 * tri + i].startVert;
const int propVert = triProperties[tri][i];
propFunc(properties + numProp * propVert, vertPos[vert],
oldProperties + numOldProp * propVert);
}
}
};
Manifold Halfspace(Box bBox, vec3 normal, double originOffset) {
normal = la::normalize(normal);
Manifold cutter = Manifold::Cube(vec3(2.0), true).Translate({1.0, 0.0, 0.0});
double size = la::length(bBox.Center() - normal * originOffset) +
0.5 * la::length(bBox.Size());
cutter = cutter.Scale(vec3(size)).Translate({originOffset, 0.0, 0.0});
double yDeg = degrees(-std::asin(normal.z));
double zDeg = degrees(std::atan2(normal.y, normal.x));
return cutter.Rotate(0.0, yDeg, zDeg);
}
template <typename Precision, typename I>
MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
int normalIdx) {
ZoneScoped;
const int numProp = impl.NumProp();
const int numVert = impl.NumPropVert();
const int numTri = impl.NumTri();
const bool isOriginal = impl.meshRelation_.originalID >= 0;
const bool updateNormals = !isOriginal && normalIdx >= 0;
MeshGLP<Precision, I> out;
out.numProp = 3 + numProp;
out.tolerance = impl.tolerance_;
if (std::is_same<Precision, float>::value)
out.tolerance =
std::max(out.tolerance,
static_cast<Precision>(std::numeric_limits<float>::epsilon() *
impl.bBox_.Scale()));
out.triVerts.resize(3 * numTri);
const int numHalfedge = impl.halfedgeTangent_.size();
out.halfedgeTangent.resize(4 * numHalfedge);
for (int i = 0; i < numHalfedge; ++i) {
const vec4 t = impl.halfedgeTangent_[i];
out.halfedgeTangent[4 * i] = t.x;
out.halfedgeTangent[4 * i + 1] = t.y;
out.halfedgeTangent[4 * i + 2] = t.z;
out.halfedgeTangent[4 * i + 3] = t.w;
}
// Sort the triangles into runs
out.faceID.resize(numTri);
std::vector<int> triNew2Old(numTri);
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
VecView<const TriRef> triRef = impl.meshRelation_.triRef;
// Don't sort originals - keep them in order
if (!isOriginal) {
std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) {
return triRef[a].originalID == triRef[b].originalID
? triRef[a].meshID < triRef[b].meshID
: triRef[a].originalID < triRef[b].originalID;
});
}
std::vector<mat3> runNormalTransform;
auto addRun = [updateNormals, isOriginal](
MeshGLP<Precision, I>& out,
std::vector<mat3>& runNormalTransform, int tri,
const manifold::Manifold::Impl::Relation& rel) {
out.runIndex.push_back(3 * tri);
out.runOriginalID.push_back(rel.originalID);
if (updateNormals) {
runNormalTransform.push_back(NormalTransform(rel.transform) *
(rel.backSide ? -1.0 : 1.0));
}
if (!isOriginal) {
for (const int col : {0, 1, 2, 3}) {
for (const int row : {0, 1, 2}) {
out.runTransform.push_back(rel.transform[col][row]);
}
}
}
};
auto meshIDtransform = impl.meshRelation_.meshIDtransform;
int lastID = -1;
for (int tri = 0; tri < numTri; ++tri) {
const int oldTri = triNew2Old[tri];
const auto ref = triRef[oldTri];
const int meshID = ref.meshID;
out.faceID[tri] = ref.tri;
for (const int i : {0, 1, 2})
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
if (meshID != lastID) {
manifold::Manifold::Impl::Relation rel;
auto it = meshIDtransform.find(meshID);
if (it != meshIDtransform.end()) rel = it->second;
addRun(out, runNormalTransform, tri, rel);
meshIDtransform.erase(meshID);
lastID = meshID;
}
}
// Add runs for originals that did not contribute any faces to the output
for (const auto& pair : meshIDtransform) {
addRun(out, runNormalTransform, numTri, pair.second);
}
out.runIndex.push_back(3 * numTri);
// Early return for no props
if (numProp == 0) {
out.vertProperties.resize(3 * numVert);
for (int i = 0; i < numVert; ++i) {
const vec3 v = impl.vertPos_[i];
out.vertProperties[3 * i] = v.x;
out.vertProperties[3 * i + 1] = v.y;
out.vertProperties[3 * i + 2] = v.z;
}
return out;
}
// Duplicate verts with different props
std::vector<int> vert2idx(impl.NumVert(), -1);
std::vector<std::vector<ivec2>> vertPropPair(impl.NumVert());
out.vertProperties.reserve(numVert * static_cast<size_t>(out.numProp));
for (size_t run = 0; run < out.runOriginalID.size(); ++run) {
for (size_t tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3;
++tri) {
const ivec3 triProp = impl.meshRelation_.triProperties[triNew2Old[tri]];
for (const int i : {0, 1, 2}) {
const int prop = triProp[i];
const int vert = out.triVerts[3 * tri + i];
auto& bin = vertPropPair[vert];
bool bFound = false;
for (const auto& b : bin) {
if (b.x == prop) {
bFound = true;
out.triVerts[3 * tri + i] = b.y;
break;
}
}
if (bFound) continue;
const int idx = out.vertProperties.size() / out.numProp;
out.triVerts[3 * tri + i] = idx;
bin.push_back({prop, idx});
for (int p : {0, 1, 2}) {
out.vertProperties.push_back(impl.vertPos_[vert][p]);
}
for (int p = 0; p < numProp; ++p) {
out.vertProperties.push_back(
impl.meshRelation_.properties[prop * numProp + p]);
}
if (updateNormals) {
vec3 normal;
const int start = out.vertProperties.size() - out.numProp;
for (int i : {0, 1, 2}) {
normal[i] = out.vertProperties[start + 3 + normalIdx + i];
}
normal = la::normalize(runNormalTransform[run] * normal);
for (int i : {0, 1, 2}) {
out.vertProperties[start + 3 + normalIdx + i] = normal[i];
}
}
if (vert2idx[vert] == -1) {
vert2idx[vert] = idx;
} else {
out.mergeFromVert.push_back(idx);
out.mergeToVert.push_back(vert2idx[vert]);
}
}
}
}
return out;
}
} // namespace
namespace manifold {
/**
* Construct an empty Manifold.
*
*/
Manifold::Manifold() : pNode_{std::make_shared<CsgLeafNode>()} {}
Manifold::~Manifold() = default;
Manifold::Manifold(Manifold&&) noexcept = default;
Manifold& Manifold::operator=(Manifold&&) noexcept = default;
Manifold::Manifold(const Manifold& other) : pNode_(other.pNode_) {}
Manifold::Manifold(std::shared_ptr<CsgNode> pNode) : pNode_(pNode) {}
Manifold::Manifold(std::shared_ptr<Impl> pImpl_)
: pNode_(std::make_shared<CsgLeafNode>(pImpl_)) {}
Manifold Manifold::Invalid() {
auto pImpl_ = std::make_shared<Impl>();
pImpl_->status_ = Error::InvalidConstruction;
return Manifold(pImpl_);
}
Manifold& Manifold::operator=(const Manifold& other) {
if (this != &other) {
pNode_ = other.pNode_;
}
return *this;
}
CsgLeafNode& Manifold::GetCsgLeafNode() const {
if (pNode_->GetNodeType() != CsgNodeType::Leaf) {
pNode_ = pNode_->ToLeafNode();
}
return *std::static_pointer_cast<CsgLeafNode>(pNode_);
}
/**
* Convert a MeshGL into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will return an empty Manifold
* and set an Error Status if the result is not an oriented 2-manifold. Will
* collapse degenerate triangles and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless round-trip
* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a
* unique originalID for each material, and sort the materials into triangle
* runs.
*
* @param meshGL The input MeshGL.
*/
Manifold::Manifold(const MeshGL& meshGL)
: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(meshGL))) {}
/**
* Convert a MeshGL into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will return an empty Manifold
* and set an Error Status if the result is not an oriented 2-manifold. Will
* collapse degenerate triangles and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless round-trip
* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a
* unique originalID for each material, and sort the materials into triangle
* runs.
*
* @param meshGL64 The input MeshGL64.
*/
Manifold::Manifold(const MeshGL64& meshGL64)
: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(meshGL64))) {}
/**
* The most complete output of this library, returning a MeshGL that is designed
* to easily push into a renderer, including all interleaved vertex properties
* that may have been input. It also includes relations to all the input meshes
* that form a part of this result and the transforms applied to each.
*
* @param normalIdx If the original MeshGL inputs that formed this manifold had
* properties corresponding to normal vectors, you can specify the first of the
* three consecutive property channels forming the (x, y, z) normals, which will
* cause this output MeshGL to automatically update these normals according to
* the applied transforms and front/back side. normalIdx + 3 must be <=
* numProp, and all original MeshGLs must use the same channels for their
* normals.
*/
MeshGL Manifold::GetMeshGL(int normalIdx) const {
const Impl& impl = *GetCsgLeafNode().GetImpl();
return GetMeshGLImpl<float, uint32_t>(impl, normalIdx);
}
/**
* The most complete output of this library, returning a MeshGL that is designed
* to easily push into a renderer, including all interleaved vertex properties
* that may have been input. It also includes relations to all the input meshes
* that form a part of this result and the transforms applied to each.
*
* @param normalIdx If the original MeshGL inputs that formed this manifold had
* properties corresponding to normal vectors, you can specify the first of the
* three consecutive property channels forming the (x, y, z) normals, which will
* cause this output MeshGL to automatically update these normals according to
* the applied transforms and front/back side. normalIdx + 3 must be <=
* numProp, and all original MeshGLs must use the same channels for their
* normals.
*/
MeshGL64 Manifold::GetMeshGL64(int normalIdx) const {
const Impl& impl = *GetCsgLeafNode().GetImpl();
return GetMeshGLImpl<double, uint64_t>(impl, normalIdx);
}
/**
* Does the Manifold have any triangles?
*/
bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); }
/**
* Returns the reason for an input Mesh producing an empty Manifold. This Status
* only applies to Manifolds newly-created from an input Mesh - once they are
* combined into a new Manifold via operations, the status reverts to NoError,
* simply processing the problem mesh as empty. Likewise, empty meshes may still
* show NoError, for instance if they are small enough relative to their
* tolerance to be collapsed to nothing.
*/
Manifold::Error Manifold::Status() const {
return GetCsgLeafNode().GetImpl()->status_;
}
/**
* The number of vertices in the Manifold.
*/
size_t Manifold::NumVert() const {
return GetCsgLeafNode().GetImpl()->NumVert();
}
/**
* The number of edges in the Manifold.
*/
size_t Manifold::NumEdge() const {
return GetCsgLeafNode().GetImpl()->NumEdge();
}
/**
* The number of triangles in the Manifold.
*/
size_t Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
/**
* The number of properties per vertex in the Manifold.
*/
size_t Manifold::NumProp() const {
return GetCsgLeafNode().GetImpl()->NumProp();
}
/**
* The number of property vertices in the Manifold. This will always be >=
* NumVert, as some physical vertices may be duplicated to account for different
* properties on different neighboring triangles.
*/
size_t Manifold::NumPropVert() const {
return GetCsgLeafNode().GetImpl()->NumPropVert();
}
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
Box Manifold::BoundingBox() const { return GetCsgLeafNode().GetImpl()->bBox_; }
/**
* Returns the epsilon value of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. This is the value of ε defining
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
double Manifold::GetEpsilon() const {
return GetCsgLeafNode().GetImpl()->epsilon_;
}
/**
* Returns the tolerance value of this Manifold. Triangles that are coplanar
* within tolerance tend to be merged and edges shorter than tolerance tend to
* be collapsed.
*/
double Manifold::GetTolerance() const {
return GetCsgLeafNode().GetImpl()->tolerance_;
}
/**
* Return a copy of the manifold with the set tolerance value.
* This performs mesh simplification when the tolerance value is increased.
*/
Manifold Manifold::SetTolerance(double tolerance) const {
auto impl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (tolerance > impl->tolerance_) {
impl->tolerance_ = tolerance;
impl->CreateFaces();
impl->SimplifyTopology();
impl->Finish();
} else {
// for reducing tolerance, we need to make sure it is still at least
// equal to epsilon.
impl->tolerance_ = std::max(impl->epsilon_, tolerance);
}
return Manifold(impl);
}
/**
* The genus is a topological property of the manifold, representing the number
* of "handles". A sphere is 0, torus 1, etc. It is only meaningful for a single
* mesh, so it is best to call Decompose() first.
*/
int Manifold::Genus() const {
int chi = NumVert() - NumEdge() + NumTri();
return 1 - chi / 2;
}
/**
* Returns the surface area of the manifold.
*/
double Manifold::SurfaceArea() const {
return GetCsgLeafNode().GetImpl()->GetProperty(Impl::Property::SurfaceArea);
}
/**
* Returns the volume of the manifold.
*/
double Manifold::Volume() const {
return GetCsgLeafNode().GetImpl()->GetProperty(Impl::Property::Volume);
}
/**
* If this mesh is an original, this returns its meshID that can be referenced
* by product manifolds' MeshRelation. If this manifold is a product, this
* returns -1.
*/
int Manifold::OriginalID() const {
return GetCsgLeafNode().GetImpl()->meshRelation_.originalID;
}
/**
* This removes all relations (originalID, faceID, transform) to ancestor meshes
* and this new Manifold is marked an original. It also collapses colinear edges
* - these don't get collapsed at boundaries where originalID changes, so the
* reset may allow flat faces to be further simplified.
*/
Manifold Manifold::AsOriginal() const {
auto oldImpl = GetCsgLeafNode().GetImpl();
if (oldImpl->status_ != Error::NoError) {
auto newImpl = std::make_shared<Impl>();
newImpl->status_ = oldImpl->status_;
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
}
auto newImpl = std::make_shared<Impl>(*oldImpl);
newImpl->InitializeOriginal();
newImpl->CreateFaces();
newImpl->SimplifyTopology();
newImpl->Finish();
newImpl->InitializeOriginal(true);
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
}
/**
* Returns the first of n sequential new unique mesh IDs for marking sets of
* triangles that can be looked up after further operations. Assign to
* MeshGL.runOriginalID vector.
*/
uint32_t Manifold::ReserveIDs(uint32_t n) {
return Manifold::Impl::ReserveIDs(n);
}
/**
* The triangle normal vectors are saved over the course of operations rather
* than recalculated to avoid rounding error. This checks that triangles still
* match their normal vectors within Precision().
*/
bool Manifold::MatchesTriNormals() const {
return GetCsgLeafNode().GetImpl()->MatchesTriNormals();
}
/**
* The number of triangles that are colinear within Precision(). This library
* attempts to remove all of these, but it cannot always remove all of them
* without changing the mesh by too much.
*/
size_t Manifold::NumDegenerateTris() const {
return GetCsgLeafNode().GetImpl()->NumDegenerateTris();
}
/**
* This is a checksum-style verification of the collider, simply returning the
* total number of edge-face bounding box overlaps between this and other.
*
* @param other A Manifold to overlap with.
*/
size_t Manifold::NumOverlaps(const Manifold& other) const {
SparseIndices overlaps = GetCsgLeafNode().GetImpl()->EdgeCollisions(
*other.GetCsgLeafNode().GetImpl());
int num_overlaps = overlaps.size();
overlaps = other.GetCsgLeafNode().GetImpl()->EdgeCollisions(
*GetCsgLeafNode().GetImpl());
return num_overlaps + overlaps.size();
}
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
Manifold Manifold::Translate(vec3 v) const {
return Manifold(pNode_->Translate(v));
}
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
Manifold Manifold::Scale(vec3 v) const { return Manifold(pNode_->Scale(v)); }
/**
* Applies an Euler angle rotation to the manifold, first about the X axis, then
* Y, then Z, in degrees. We use degrees so that we can minimize rounding error,
* and eliminate it completely for any multiples of 90 degrees. Additionally,
* more efficient code paths are used to update the manifold when the transforms
* only rotate by multiples of 90 degrees. This operation can be chained.
* Transforms are combined and applied lazily.
*
* @param xDegrees First rotation, degrees about the X-axis.
* @param yDegrees Second rotation, degrees about the Y-axis.
* @param zDegrees Third rotation, degrees about the Z-axis.
*/
Manifold Manifold::Rotate(double xDegrees, double yDegrees,
double zDegrees) const {
return Manifold(pNode_->Rotate(xDegrees, yDegrees, zDegrees));
}
/**
* Transform this Manifold in space. The first three columns form a 3x3 matrix
* transform and the last is a translation vector. This operation can be
* chained. Transforms are combined and applied lazily.
*
* @param m The affine transform matrix to apply to all the vertices.
*/
Manifold Manifold::Transform(const mat3x4& m) const {
return Manifold(pNode_->Transform(m));
}
/**
* Mirror this Manifold over the plane described by the unit form of the given
* normal vector. If the length of the normal is zero, an empty Manifold is
* returned. This operation can be chained. Transforms are combined and applied
* lazily.
*
* @param normal The normal vector of the plane to be mirrored over
*/
Manifold Manifold::Mirror(vec3 normal) const {
if (la::length(normal) == 0.) {
return Manifold();
}
auto n = la::normalize(normal);
auto m = mat3x4(mat3(la::identity) - 2.0 * la::outerprod(n, n), vec3());
return Manifold(pNode_->Transform(m));
}
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps, but
* that is not checked here, so it is up to the user to choose their function
* with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
Manifold Manifold::Warp(std::function<void(vec3&)> warpFunc) const {
auto oldImpl = GetCsgLeafNode().GetImpl();
if (oldImpl->status_ != Error::NoError) {
auto pImpl = std::make_shared<Impl>();
pImpl->status_ = oldImpl->status_;
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
auto pImpl = std::make_shared<Impl>(*oldImpl);
pImpl->Warp(warpFunc);
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Same as Manifold::Warp but calls warpFunc with with
* a VecView which is roughly equivalent to std::span
* pointing to all vec3 elements to be modified in-place
*
* @param warpFunc A function that modifies multiple vertex positions.
*/
Manifold Manifold::WarpBatch(
std::function<void(VecView<vec3>)> warpFunc) const {
auto oldImpl = GetCsgLeafNode().GetImpl();
if (oldImpl->status_ != Error::NoError) {
auto pImpl = std::make_shared<Impl>();
pImpl->status_ = oldImpl->status_;
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
auto pImpl = std::make_shared<Impl>(*oldImpl);
pImpl->WarpBatch(warpFunc);
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Create a new copy of this manifold with updated vertex properties by
* supplying a function that takes the existing position and properties as
* input. You may specify any number of output properties, allowing creation and
* removal of channels. Note: undefined behavior will result if you read past
* the number of input properties or write past the number of output properties.
*
* If propFunc is a nullptr, this function will just set the channel to zeroes.
*
* @param numProp The new number of properties per vertex.
* @param propFunc A function that modifies the properties of a given vertex.
*/
Manifold Manifold::SetProperties(
int numProp,
std::function<void(double* newProp, vec3 position, const double* oldProp)>
propFunc) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
const int oldNumProp = NumProp();
const Vec<double> oldProperties = pImpl->meshRelation_.properties;
auto& triProperties = pImpl->meshRelation_.triProperties;
if (numProp == 0) {
triProperties.resize(0);
pImpl->meshRelation_.properties.resize(0);
} else {
if (triProperties.size() == 0) {
const int numTri = NumTri();
triProperties.resize(numTri);
for (int i = 0; i < numTri; ++i) {
for (const int j : {0, 1, 2}) {
triProperties[i][j] = pImpl->halfedge_[3 * i + j].startVert;
}
}
pImpl->meshRelation_.properties = Vec<double>(numProp * NumVert(), 0);
} else {
pImpl->meshRelation_.properties = Vec<double>(numProp * NumPropVert(), 0);
}
for_each_n(
propFunc == nullptr ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
countAt(0), NumTri(),
UpdateProperties(
{pImpl->meshRelation_.properties.data(), numProp,
oldProperties.data(), oldNumProp, pImpl->vertPos_.data(),
triProperties.data(), pImpl->halfedge_.data(),
propFunc == nullptr ? [](double* newProp, vec3 position,
const double* oldProp) { *newProp = 0; }
: propFunc}));
}
pImpl->meshRelation_.numProp = numProp;
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex and assigns
* them as vertex properties on the given channels.
*
* @param gaussianIdx The property channel index in which to store the Gaussian
* curvature. An index < 0 will be ignored (stores nothing). The property set
* will be automatically expanded to include the channel index specified.
*
* @param meanIdx The property channel index in which to store the mean
* curvature. An index < 0 will be ignored (stores nothing). The property set
* will be automatically expanded to include the channel index specified.
*/
Manifold Manifold::CalculateCurvature(int gaussianIdx, int meanIdx) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
pImpl->CalculateCurvature(gaussianIdx, meanIdx);
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Fills in vertex properties for normal vectors, calculated from the mesh
* geometry. Flat faces composed of three or more triangles will remain flat.
*
* @param normalIdx The property channel in which to store the X
* values of the normals. The X, Y, and Z channels will be sequential. The
* property set will be automatically expanded such that NumProp will be at
* least normalIdx + 3.
*
* @param minSharpAngle Any edges with angles greater than this value will
* remain sharp, getting different normal vector properties on each side of the
* edge. By default, no edges are sharp and all normals are shared. With a value
* of zero, the model is faceted and all normals match their triangle normals,
* but in this case it would be better not to calculate normals at all.
*/
Manifold Manifold::CalculateNormals(int normalIdx, double minSharpAngle) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
pImpl->SetNormals(normalIdx, minSharpAngle);
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
* geometry will remain unchanged until Refine or RefineToLength is called to
* interpolate the surface. This version uses the supplied vertex normal
* properties to define the tangent vectors. Faces of two coplanar triangles
* will be marked as quads, while faces with three or more will be flat.
*
* @param normalIdx The first property channel of the normals. NumProp must be
* at least normalIdx + 3. Any vertex where multiple normals exist and don't
* agree will result in a sharp edge.
*/
Manifold Manifold::SmoothByNormals(int normalIdx) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (!IsEmpty()) {
pImpl->CreateTangents(normalIdx);
}
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Smooths out the Manifold by filling in the halfedgeTangent vectors. The
* geometry will remain unchanged until Refine or RefineToLength is called to
* interpolate the surface. This version uses the geometry of the triangles and
* pseudo-normals to define the tangent vectors. Faces of two coplanar triangles
* will be marked as quads.
*
* @param minSharpAngle degrees, default 60. Any edges with angles greater than
* this value will remain sharp. The rest will be smoothed to G1 continuity,
* with the caveat that flat faces of three or more triangles will always remain
* flat. With a value of zero, the model is faceted, but in this case there is
* no point in smoothing.
*
* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to sharp
* angles. The default gives a hard edge, while values > 0 will give a small
* fillet on these sharp edges. A value of 1 is equivalent to a minSharpAngle of
* 180 - all edges will be smooth.
*/
Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (!IsEmpty()) {
if (minSmoothness == 0) {
const int numProp = pImpl->meshRelation_.numProp;
Vec<double> properties = pImpl->meshRelation_.properties;
Vec<ivec3> triProperties = pImpl->meshRelation_.triProperties;
pImpl->SetNormals(0, minSharpAngle);
pImpl->CreateTangents(0);
pImpl->meshRelation_.numProp = numProp;
pImpl->meshRelation_.properties.swap(properties);
pImpl->meshRelation_.triProperties.swap(triProperties);
} else {
pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness));
}
}
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. Quads
* will ignore their interior triangle bisector. These will all be coplanar (and
* will not be immediately collapsed) unless the Mesh/Manifold has
* halfedgeTangents specified (e.g. from the Smooth() constructor), in which
* case the new vertices will be moved to the interpolated surface according to
* their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
Manifold Manifold::Refine(int n) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (n > 1) {
pImpl->Refine(
[n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; });
}
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Increase the density of the mesh by splitting each edge into pieces of
* roughly the input length. Interior verts are added to keep the rest of the
* triangulation edges also of roughly the same length. If halfedgeTangents are
* present (e.g. from the Smooth() constructor), the new vertices will be moved
* to the interpolated surface according to their barycentric coordinates. Quads
* will ignore their interior triangle bisector.
*
* @param length The length that edges will be broken down to.
*/
Manifold Manifold::RefineToLength(double length) const {
length = std::abs(length);
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
pImpl->Refine([length](vec3 edge, vec4 tangentStart, vec4 tangentEnd) {
return static_cast<int>(la::length(edge) / length);
});
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* Increase the density of the mesh by splitting each edge into pieces such that
* any point on the resulting triangles is roughly within tolerance of the
* smoothly curved surface defined by the tangent vectors. This means tightly
* curving regions will be divided more finely than smoother regions. If
* halfedgeTangents are not present, the result will simply be a copy of the
* original. Quads will ignore their interior triangle bisector.
*
* @param tolerance The desired maximum distance between the faceted mesh
* produced and the exact smoothly curving surface. All vertices are exactly on
* the surface, within rounding error.
*/
Manifold Manifold::RefineToTolerance(double tolerance) const {
tolerance = std::abs(tolerance);
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
if (!pImpl->halfedgeTangent_.empty()) {
pImpl->Refine(
[tolerance](vec3 edge, vec4 tangentStart, vec4 tangentEnd) {
const vec3 edgeNorm = la::normalize(edge);
// Weight heuristic
const vec3 tStart = vec3(tangentStart);
const vec3 tEnd = vec3(tangentEnd);
// Perpendicular to edge
const vec3 start = tStart - edgeNorm * la::dot(edgeNorm, tStart);
const vec3 end = tEnd - edgeNorm * la::dot(edgeNorm, tEnd);
// Circular arc result plus heuristic term for non-circular curves
const double d = 0.5 * (la::length(start) + la::length(end)) +
la::length(start - end);
return static_cast<int>(std::sqrt(3 * d / (4 * tolerance)));
},
true);
}
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}
/**
* The central operation of this library: the Boolean combines two manifolds
* into another by calculating their intersections and removing the unused
* portions.
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid)
* inputs will produce ε-valid output. ε-invalid input may fail
* triangulation.
*
* These operations are optimized to produce nearly-instant results if either
* input is empty or their bounding boxes do not overlap.
*
* @param second The other Manifold.
* @param op The type of operation to perform.
*/
Manifold Manifold::Boolean(const Manifold& second, OpType op) const {
return Manifold(pNode_->Boolean(second.pNode_, op));
}
/**
* Perform the given boolean operation on a list of Manifolds. In case of
* Subtract, all Manifolds in the tail are differenced from the head.
*/
Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,
OpType op) {
if (manifolds.size() == 0)
return Manifold();
else if (manifolds.size() == 1)
return manifolds[0];
std::vector<std::shared_ptr<CsgNode>> children;
children.reserve(manifolds.size());
for (const auto& m : manifolds) children.push_back(m.pNode_);
return Manifold(std::make_shared<CsgOpNode>(children, op));
}
/**
* Shorthand for Boolean Union.
*/
Manifold Manifold::operator+(const Manifold& Q) const {
return Boolean(Q, OpType::Add);
}
/**
* Shorthand for Boolean Union assignment.
*/
Manifold& Manifold::operator+=(const Manifold& Q) {
*this = *this + Q;
return *this;
}
/**
* Shorthand for Boolean Difference.
*/
Manifold Manifold::operator-(const Manifold& Q) const {
return Boolean(Q, OpType::Subtract);
}
/**
* Shorthand for Boolean Difference assignment.
*/
Manifold& Manifold::operator-=(const Manifold& Q) {
*this = *this - Q;
return *this;
}
/**
* Shorthand for Boolean Intersection.
*/
Manifold Manifold::operator^(const Manifold& Q) const {
return Boolean(Q, OpType::Intersect);
}
/**
* Shorthand for Boolean Intersection assignment.
*/
Manifold& Manifold::operator^=(const Manifold& Q) {
*this = *this ^ Q;
return *this;
}
/**
* Split cuts this manifold in two using the cutter manifold. The first result
* is the intersection, second is the difference. This is more efficient than
* doing them separately.
*
* @param cutter
*/
std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {
auto impl1 = GetCsgLeafNode().GetImpl();
auto impl2 = cutter.GetCsgLeafNode().GetImpl();
Boolean3 boolean(*impl1, *impl2, OpType::Subtract);
auto result1 = std::make_shared<CsgLeafNode>(
std::make_unique<Impl>(boolean.Result(OpType::Intersect)));
auto result2 = std::make_shared<CsgLeafNode>(
std::make_unique<Impl>(boolean.Result(OpType::Subtract)));
return std::make_pair(Manifold(result1), Manifold(result2));
}
/**
* Convenient version of Split() for a half-space.
*
* @param normal This vector is normal to the cutting plane and its length does
* not matter. The first result is in the direction of this vector, the second
* result is on the opposite side.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
std::pair<Manifold, Manifold> Manifold::SplitByPlane(
vec3 normal, double originOffset) const {
return Split(Halfspace(BoundingBox(), normal, originOffset));
}
/**
* Identical to SplitByPlane(), but calculating and returning only the first
* result.
*
* @param normal This vector is normal to the cutting plane and its length does
* not matter. The result is in the direction of this vector from the plane.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
Manifold Manifold::TrimByPlane(vec3 normal, double originOffset) const {
return *this ^ Halfspace(BoundingBox(), normal, originOffset);
}
/**
* Returns the cross section of this object parallel to the X-Y plane at the
* specified Z height, defaulting to zero. Using a height equal to the bottom of
* the bounding box will return the bottom faces, while using a height equal to
* the top of the bounding box will return empty.
*/
Polygons Manifold::Slice(double height) const {
return GetCsgLeafNode().GetImpl()->Slice(height);
}
/**
* Returns polygons representing the projected outline of this object
* onto the X-Y plane. These polygons will often self-intersect, so it is
* recommended to run them through the positive fill rule of CrossSection to get
* a sensible result before using them.
*/
Polygons Manifold::Project() const {
return GetCsgLeafNode().GetImpl()->Project();
}
ExecutionParams& ManifoldParams() { return manifoldParams; }
/**
* Compute the convex hull of a set of points. If the given points are fewer
* than 4, or they are all coplanar, an empty Manifold will be returned.
*
* @param pts A vector of 3-dimensional points over which to compute a convex
* hull.
*/
Manifold Manifold::Hull(const std::vector<vec3>& pts) {
std::shared_ptr<Impl> impl = std::make_shared<Impl>();
impl->Hull(Vec<vec3>(pts));
return Manifold(std::make_shared<CsgLeafNode>(impl));
}
/**
* Compute the convex hull of this manifold.
*/
Manifold Manifold::Hull() const {
std::shared_ptr<Impl> impl = std::make_shared<Impl>();
impl->Hull(GetCsgLeafNode().GetImpl()->vertPos_);
return Manifold(std::make_shared<CsgLeafNode>(impl));
}
/**
* Compute the convex hull enveloping a set of manifolds.
*
* @param manifolds A vector of manifolds over which to compute a convex hull.
*/
Manifold Manifold::Hull(const std::vector<Manifold>& manifolds) {
return Compose(manifolds).Hull();
}
/**
* Returns the minimum gap between two manifolds. Returns a double between
* 0 and searchLength.
*
* @param other The other manifold to compute the minimum gap to.
* @param searchLength The maximum distance to search for a minimum gap.
*/
double Manifold::MinGap(const Manifold& other, double searchLength) const {
auto intersect = *this ^ other;
if (!intersect.IsEmpty()) return 0.0;
return GetCsgLeafNode().GetImpl()->MinGap(*other.GetCsgLeafNode().GetImpl(),
searchLength);
}
} // namespace manifold