Path: blob/master/thirdparty/manifold/src/manifold.cpp
20882 views
// Copyright 2021 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include <algorithm>15#include <map>16#include <numeric>1718#include "boolean3.h"19#include "csg_tree.h"20#include "impl.h"21#include "parallel.h"22#include "shared.h"2324namespace {25using namespace manifold;2627ExecutionParams manifoldParams;2829Manifold Halfspace(Box bBox, vec3 normal, double originOffset) {30normal = la::normalize(normal);31Manifold cutter = Manifold::Cube(vec3(2.0), true).Translate({1.0, 0.0, 0.0});32double size = la::length(bBox.Center() - normal * originOffset) +330.5 * la::length(bBox.Size());34cutter = cutter.Scale(vec3(size)).Translate({originOffset, 0.0, 0.0});35double yDeg = degrees(-std::asin(normal.z));36double zDeg = degrees(std::atan2(normal.y, normal.x));37return cutter.Rotate(0.0, yDeg, zDeg);38}3940template <typename Precision, typename I>41MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,42int normalIdx) {43ZoneScoped;44const int numProp = impl.NumProp();45const int numVert = impl.NumPropVert();46const int numTri = impl.NumTri();4748const bool isOriginal = impl.meshRelation_.originalID >= 0;49const bool updateNormals = !isOriginal && normalIdx >= 0;5051MeshGLP<Precision, I> out;52out.numProp = 3 + numProp;53out.tolerance = impl.tolerance_;54if (std::is_same<Precision, float>::value)55out.tolerance =56std::max(out.tolerance,57static_cast<Precision>(std::numeric_limits<float>::epsilon() *58impl.bBox_.Scale()));59out.triVerts.resize(3 * numTri);6061const int numHalfedge = impl.halfedgeTangent_.size();62out.halfedgeTangent.resize(4 * numHalfedge);63for (int i = 0; i < numHalfedge; ++i) {64const vec4 t = impl.halfedgeTangent_[i];65out.halfedgeTangent[4 * i] = t.x;66out.halfedgeTangent[4 * i + 1] = t.y;67out.halfedgeTangent[4 * i + 2] = t.z;68out.halfedgeTangent[4 * i + 3] = t.w;69}70// Sort the triangles into runs71out.faceID.resize(numTri);72std::vector<int> triNew2Old(numTri);73std::iota(triNew2Old.begin(), triNew2Old.end(), 0);74VecView<const TriRef> triRef = impl.meshRelation_.triRef;75// Don't sort originals - keep them in order76if (!isOriginal) {77std::stable_sort(triNew2Old.begin(), triNew2Old.end(),78[triRef](int a, int b) {79return triRef[a].originalID == triRef[b].originalID80? triRef[a].meshID < triRef[b].meshID81: triRef[a].originalID < triRef[b].originalID;82});83}8485std::vector<mat3> runNormalTransform;86auto addRun = [updateNormals, isOriginal](87MeshGLP<Precision, I>& out,88std::vector<mat3>& runNormalTransform, int tri,89const manifold::Manifold::Impl::Relation& rel) {90out.runIndex.push_back(3 * tri);91out.runOriginalID.push_back(rel.originalID);92if (updateNormals) {93runNormalTransform.push_back(NormalTransform(rel.transform) *94(rel.backSide ? -1.0 : 1.0));95}96if (!isOriginal) {97for (const int col : {0, 1, 2, 3}) {98for (const int row : {0, 1, 2}) {99out.runTransform.push_back(rel.transform[col][row]);100}101}102}103};104105auto meshIDtransform = impl.meshRelation_.meshIDtransform;106int lastID = -1;107for (int tri = 0; tri < numTri; ++tri) {108const int oldTri = triNew2Old[tri];109const auto ref = triRef[oldTri];110const int meshID = ref.meshID;111112out.faceID[tri] = ref.faceID >= 0 ? ref.faceID : ref.coplanarID;113for (const int i : {0, 1, 2})114out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;115116if (meshID != lastID) {117manifold::Manifold::Impl::Relation rel;118auto it = meshIDtransform.find(meshID);119if (it != meshIDtransform.end()) rel = it->second;120addRun(out, runNormalTransform, tri, rel);121meshIDtransform.erase(meshID);122lastID = meshID;123}124}125// Add runs for originals that did not contribute any faces to the output126for (const auto& pair : meshIDtransform) {127addRun(out, runNormalTransform, numTri, pair.second);128}129out.runIndex.push_back(3 * numTri);130131// Early return for no props132if (numProp == 0) {133out.vertProperties.resize(3 * numVert);134for (int i = 0; i < numVert; ++i) {135const vec3 v = impl.vertPos_[i];136out.vertProperties[3 * i] = v.x;137out.vertProperties[3 * i + 1] = v.y;138out.vertProperties[3 * i + 2] = v.z;139}140return out;141}142// Duplicate verts with different props143std::vector<int> vert2idx(impl.NumVert(), -1);144std::vector<std::vector<ivec2>> vertPropPair(impl.NumVert());145out.vertProperties.reserve(numVert * static_cast<size_t>(out.numProp));146147for (size_t run = 0; run < out.runOriginalID.size(); ++run) {148for (size_t tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3;149++tri) {150for (const int i : {0, 1, 2}) {151const int prop = impl.halfedge_[3 * triNew2Old[tri] + i].propVert;152const int vert = out.triVerts[3 * tri + i];153154auto& bin = vertPropPair[vert];155bool bFound = false;156for (const auto& b : bin) {157if (b.x == prop) {158bFound = true;159out.triVerts[3 * tri + i] = b.y;160break;161}162}163if (bFound) continue;164const int idx = out.vertProperties.size() / out.numProp;165out.triVerts[3 * tri + i] = idx;166bin.push_back({prop, idx});167168for (int p : {0, 1, 2}) {169out.vertProperties.push_back(impl.vertPos_[vert][p]);170}171for (int p = 0; p < numProp; ++p) {172out.vertProperties.push_back(impl.properties_[prop * numProp + p]);173}174175if (updateNormals) {176vec3 normal;177const int start = out.vertProperties.size() - out.numProp;178for (int i : {0, 1, 2}) {179normal[i] = out.vertProperties[start + 3 + normalIdx + i];180}181normal = la::normalize(runNormalTransform[run] * normal);182for (int i : {0, 1, 2}) {183out.vertProperties[start + 3 + normalIdx + i] = normal[i];184}185}186187if (vert2idx[vert] == -1) {188vert2idx[vert] = idx;189} else {190out.mergeFromVert.push_back(idx);191out.mergeToVert.push_back(vert2idx[vert]);192}193}194}195}196return out;197}198} // namespace199200namespace manifold {201202static int circularSegments_ = DEFAULT_SEGMENTS;203static double circularAngle_ = DEFAULT_ANGLE;204static double circularEdgeLength_ = DEFAULT_LENGTH;205206/**207* Sets an angle constraint the default number of circular segments for the208* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and209* Manifold::Revolve() constructors. The number of segments will be rounded up210* to the nearest factor of four.211*212* @param angle The minimum angle in degrees between consecutive segments. The213* angle will increase if the the segments hit the minimum edge length.214* Default is 10 degrees.215*/216void Quality::SetMinCircularAngle(double angle) {217if (angle <= 0) return;218circularAngle_ = angle;219}220221/**222* Sets a length constraint the default number of circular segments for the223* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and224* Manifold::Revolve() constructors. The number of segments will be rounded up225* to the nearest factor of four.226*227* @param length The minimum length of segments. The length will228* increase if the the segments hit the minimum angle. Default is 1.0.229*/230void Quality::SetMinCircularEdgeLength(double length) {231if (length <= 0) return;232circularEdgeLength_ = length;233}234235/**236* Sets the default number of circular segments for the237* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and238* Manifold::Revolve() constructors. Overrides the edge length and angle239* constraints and sets the number of segments to exactly this value.240*241* @param number Number of circular segments. Default is 0, meaning no242* constraint is applied.243*/244void Quality::SetCircularSegments(int number) {245if (number < 3 && number != 0) return;246circularSegments_ = number;247}248249/**250* Determine the result of the SetMinCircularAngle(),251* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.252*253* @param radius For a given radius of circle, determine how many default254* segments there will be.255*/256int Quality::GetCircularSegments(double radius) {257if (circularSegments_ > 0) return circularSegments_;258int nSegA = 360.0 / circularAngle_;259int nSegL = 2.0 * radius * kPi / circularEdgeLength_;260int nSeg = fmin(nSegA, nSegL) + 3;261nSeg -= nSeg % 4;262return std::max(nSeg, 4);263}264265/**266* Resets the circular construction parameters to their defaults if267* SetMinCircularAngle, SetMinCircularEdgeLength, or SetCircularSegments have268* been called.269*/270void Quality::ResetToDefaults() {271circularSegments_ = DEFAULT_SEGMENTS;272circularAngle_ = DEFAULT_ANGLE;273circularEdgeLength_ = DEFAULT_LENGTH;274}275276/**277* Construct an empty Manifold.278*279*/280Manifold::Manifold() : pNode_{std::make_shared<CsgLeafNode>()} {}281Manifold::~Manifold() = default;282Manifold::Manifold(Manifold&&) noexcept = default;283Manifold& Manifold::operator=(Manifold&&) noexcept = default;284285Manifold::Manifold(const Manifold& other) : pNode_(other.pNode_) {}286287Manifold::Manifold(std::shared_ptr<CsgNode> pNode) : pNode_(pNode) {}288289Manifold::Manifold(std::shared_ptr<Impl> pImpl_)290: pNode_(std::make_shared<CsgLeafNode>(pImpl_)) {}291292Manifold Manifold::Invalid() {293auto pImpl_ = std::make_shared<Impl>();294pImpl_->status_ = Error::InvalidConstruction;295return Manifold(pImpl_);296}297298Manifold& Manifold::operator=(const Manifold& other) {299if (this != &other) {300pNode_ = other.pNode_;301}302return *this;303}304305CsgLeafNode& Manifold::GetCsgLeafNode() const {306if (pNode_->GetNodeType() != CsgNodeType::Leaf) {307pNode_ = pNode_->ToLeafNode();308}309return *std::static_pointer_cast<CsgLeafNode>(pNode_);310}311312/**313* Convert a MeshGL into a Manifold, retaining its properties and merging only314* the positions according to the merge vectors. Will return an empty Manifold315* and set an Error Status if the result is not an oriented 2-manifold. Will316* collapse degenerate triangles and unnecessary vertices.317*318* All fields are read, making this structure suitable for a lossless round-trip319* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a320* unique originalID for each material, and sort the materials into triangle321* runs.322*323* @param meshGL The input MeshGL.324*/325Manifold::Manifold(const MeshGL& meshGL)326: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(meshGL))) {}327328/**329* Convert a MeshGL into a Manifold, retaining its properties and merging only330* the positions according to the merge vectors. Will return an empty Manifold331* and set an Error Status if the result is not an oriented 2-manifold. Will332* collapse degenerate triangles and unnecessary vertices.333*334* All fields are read, making this structure suitable for a lossless round-trip335* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a336* unique originalID for each material, and sort the materials into triangle337* runs.338*339* @param meshGL64 The input MeshGL64.340*/341Manifold::Manifold(const MeshGL64& meshGL64)342: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(meshGL64))) {}343344/**345* The most complete output of this library, returning a MeshGL that is designed346* to easily push into a renderer, including all interleaved vertex properties347* that may have been input. It also includes relations to all the input meshes348* that form a part of this result and the transforms applied to each.349*350* @param normalIdx If the original MeshGL inputs that formed this manifold had351* properties corresponding to normal vectors, you can specify the first of the352* three consecutive property channels forming the (x, y, z) normals, which will353* cause this output MeshGL to automatically update these normals according to354* the applied transforms and front/back side. normalIdx + 3 must be <=355* numProp, and all original MeshGLs must use the same channels for their356* normals.357*/358MeshGL Manifold::GetMeshGL(int normalIdx) const {359const Impl& impl = *GetCsgLeafNode().GetImpl();360return GetMeshGLImpl<float, uint32_t>(impl, normalIdx);361}362363/**364* The most complete output of this library, returning a MeshGL that is designed365* to easily push into a renderer, including all interleaved vertex properties366* that may have been input. It also includes relations to all the input meshes367* that form a part of this result and the transforms applied to each.368*369* @param normalIdx If the original MeshGL inputs that formed this manifold had370* properties corresponding to normal vectors, you can specify the first of the371* three consecutive property channels forming the (x, y, z) normals, which will372* cause this output MeshGL to automatically update these normals according to373* the applied transforms and front/back side. normalIdx + 3 must be <=374* numProp, and all original MeshGLs must use the same channels for their375* normals.376*/377MeshGL64 Manifold::GetMeshGL64(int normalIdx) const {378const Impl& impl = *GetCsgLeafNode().GetImpl();379return GetMeshGLImpl<double, uint64_t>(impl, normalIdx);380}381382/**383* Does the Manifold have any triangles?384*/385bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); }386/**387* Returns the reason for an input Mesh producing an empty Manifold. This Status388* will carry on through operations like NaN propogation, ensuring an errored389* mesh doesn't get mysteriously lost. Empty meshes may still show390* NoError, for instance the intersection of non-overlapping meshes.391*/392Manifold::Error Manifold::Status() const {393return GetCsgLeafNode().GetImpl()->status_;394}395/**396* The number of vertices in the Manifold.397*/398size_t Manifold::NumVert() const {399return GetCsgLeafNode().GetImpl()->NumVert();400}401/**402* The number of edges in the Manifold.403*/404size_t Manifold::NumEdge() const {405return GetCsgLeafNode().GetImpl()->NumEdge();406}407/**408* The number of triangles in the Manifold.409*/410size_t Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }411/**412* The number of properties per vertex in the Manifold.413*/414size_t Manifold::NumProp() const {415return GetCsgLeafNode().GetImpl()->NumProp();416}417/**418* The number of property vertices in the Manifold. This will always be >=419* NumVert, as some physical vertices may be duplicated to account for different420* properties on different neighboring triangles.421*/422size_t Manifold::NumPropVert() const {423return GetCsgLeafNode().GetImpl()->NumPropVert();424}425426/**427* Returns the axis-aligned bounding box of all the Manifold's vertices.428*/429Box Manifold::BoundingBox() const { return GetCsgLeafNode().GetImpl()->bBox_; }430431/**432* Returns the epsilon value of this Manifold's vertices, which tracks the433* approximate rounding error over all the transforms and operations that have434* led to this state. This is the value of ε defining435* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).436*/437double Manifold::GetEpsilon() const {438return GetCsgLeafNode().GetImpl()->epsilon_;439}440441/**442* Returns the tolerance value of this Manifold. Triangles that are coplanar443* within tolerance tend to be merged and edges shorter than tolerance tend to444* be collapsed.445*/446double Manifold::GetTolerance() const {447return GetCsgLeafNode().GetImpl()->tolerance_;448}449450/**451* Return a copy of the manifold with the set tolerance value.452* This performs mesh simplification when the tolerance value is increased.453*/454Manifold Manifold::SetTolerance(double tolerance) const {455auto impl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());456if (tolerance > impl->tolerance_) {457impl->tolerance_ = tolerance;458impl->MarkCoplanar();459impl->SimplifyTopology();460impl->Finish();461} else {462// for reducing tolerance, we need to make sure it is still at least463// equal to epsilon.464impl->tolerance_ = std::max(impl->epsilon_, tolerance);465}466return Manifold(impl);467}468469/**470* Return a copy of the manifold simplified to the given tolerance, but with its471* actual tolerance value unchanged. If the tolerance is not given or is less472* than the current tolerance, the current tolerance is used for simplification.473* The result will contain a subset of the original verts and all surfaces will474* have moved by less than tolerance.475*/476Manifold Manifold::Simplify(double tolerance) const {477auto impl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());478const double oldTolerance = impl->tolerance_;479if (tolerance == 0) tolerance = oldTolerance;480if (tolerance > oldTolerance) {481impl->tolerance_ = tolerance;482impl->MarkCoplanar();483}484impl->SimplifyTopology();485impl->Finish();486impl->tolerance_ = oldTolerance;487return Manifold(impl);488}489490/**491* The genus is a topological property of the manifold, representing the number492* of "handles". A sphere is 0, torus 1, etc. It is only meaningful for a single493* mesh, so it is best to call Decompose() first.494*/495int Manifold::Genus() const {496int chi = NumVert() - NumEdge() + NumTri();497return 1 - chi / 2;498}499500/**501* Returns the surface area of the manifold.502*/503double Manifold::SurfaceArea() const {504return GetCsgLeafNode().GetImpl()->GetProperty(Impl::Property::SurfaceArea);505}506507/**508* Returns the volume of the manifold.509*/510double Manifold::Volume() const {511return GetCsgLeafNode().GetImpl()->GetProperty(Impl::Property::Volume);512}513514/**515* If this mesh is an original, this returns its meshID that can be referenced516* by product manifolds' MeshRelation. If this manifold is a product, this517* returns -1.518*/519int Manifold::OriginalID() const {520return GetCsgLeafNode().GetImpl()->meshRelation_.originalID;521}522523/**524* This removes all relations (originalID, faceID, transform) to ancestor meshes525* and this new Manifold is marked an original. It also recreates faces526* - these don't get joined at boundaries where originalID changes, so the527* reset may allow triangles of flat faces to be further collapsed with528* Simplify().529*/530Manifold Manifold::AsOriginal() const {531auto oldImpl = GetCsgLeafNode().GetImpl();532if (oldImpl->status_ != Error::NoError) {533auto newImpl = std::make_shared<Impl>();534newImpl->status_ = oldImpl->status_;535return Manifold(std::make_shared<CsgLeafNode>(newImpl));536}537auto newImpl = std::make_shared<Impl>(*oldImpl);538newImpl->InitializeOriginal();539newImpl->MarkCoplanar();540newImpl->InitializeOriginal(true);541return Manifold(std::make_shared<CsgLeafNode>(newImpl));542}543544/**545* Returns the first of n sequential new unique mesh IDs for marking sets of546* triangles that can be looked up after further operations. Assign to547* MeshGL.runOriginalID vector.548*/549uint32_t Manifold::ReserveIDs(uint32_t n) {550return Manifold::Impl::ReserveIDs(n);551}552553/**554* The triangle normal vectors are saved over the course of operations rather555* than recalculated to avoid rounding error. This checks that triangles still556* match their normal vectors within Precision().557*/558bool Manifold::MatchesTriNormals() const {559return GetCsgLeafNode().GetImpl()->MatchesTriNormals();560}561562/**563* The number of triangles that are colinear within Precision(). This library564* attempts to remove all of these, but it cannot always remove all of them565* without changing the mesh by too much.566*/567size_t Manifold::NumDegenerateTris() const {568return GetCsgLeafNode().GetImpl()->NumDegenerateTris();569}570571/**572* Move this Manifold in space. This operation can be chained. Transforms are573* combined and applied lazily.574*575* @param v The vector to add to every vertex.576*/577Manifold Manifold::Translate(vec3 v) const {578return Manifold(pNode_->Translate(v));579}580581/**582* Scale this Manifold in space. This operation can be chained. Transforms are583* combined and applied lazily.584*585* @param v The vector to multiply every vertex by per component.586*/587Manifold Manifold::Scale(vec3 v) const { return Manifold(pNode_->Scale(v)); }588589/**590* Applies an Euler angle rotation to the manifold, first about the X axis, then591* Y, then Z, in degrees. We use degrees so that we can minimize rounding error,592* and eliminate it completely for any multiples of 90 degrees. Additionally,593* more efficient code paths are used to update the manifold when the transforms594* only rotate by multiples of 90 degrees. This operation can be chained.595* Transforms are combined and applied lazily.596*597* @param xDegrees First rotation, degrees about the X-axis.598* @param yDegrees Second rotation, degrees about the Y-axis.599* @param zDegrees Third rotation, degrees about the Z-axis.600*/601Manifold Manifold::Rotate(double xDegrees, double yDegrees,602double zDegrees) const {603return Manifold(pNode_->Rotate(xDegrees, yDegrees, zDegrees));604}605606/**607* Transform this Manifold in space. The first three columns form a 3x3 matrix608* transform and the last is a translation vector. This operation can be609* chained. Transforms are combined and applied lazily.610*611* @param m The affine transform matrix to apply to all the vertices.612*/613Manifold Manifold::Transform(const mat3x4& m) const {614return Manifold(pNode_->Transform(m));615}616617/**618* Mirror this Manifold over the plane described by the unit form of the given619* normal vector. If the length of the normal is zero, an empty Manifold is620* returned. This operation can be chained. Transforms are combined and applied621* lazily.622*623* @param normal The normal vector of the plane to be mirrored over624*/625Manifold Manifold::Mirror(vec3 normal) const {626if (la::length(normal) == 0.) {627return Manifold();628}629auto n = la::normalize(normal);630auto m = mat3x4(mat3(la::identity) - 2.0 * la::outerprod(n, n), vec3());631return Manifold(pNode_->Transform(m));632}633634/**635* This function does not change the topology, but allows the vertices to be636* moved according to any arbitrary input function. It is easy to create a637* function that warps a geometrically valid object into one which overlaps, but638* that is not checked here, so it is up to the user to choose their function639* with discretion.640*641* @param warpFunc A function that modifies a given vertex position.642*/643Manifold Manifold::Warp(std::function<void(vec3&)> warpFunc) const {644auto oldImpl = GetCsgLeafNode().GetImpl();645if (oldImpl->status_ != Error::NoError) {646auto pImpl = std::make_shared<Impl>();647pImpl->status_ = oldImpl->status_;648return Manifold(std::make_shared<CsgLeafNode>(pImpl));649}650auto pImpl = std::make_shared<Impl>(*oldImpl);651pImpl->Warp(warpFunc);652return Manifold(std::make_shared<CsgLeafNode>(pImpl));653}654655/**656* Same as Manifold::Warp but calls warpFunc with with657* a VecView which is roughly equivalent to std::span658* pointing to all vec3 elements to be modified in-place659*660* @param warpFunc A function that modifies multiple vertex positions.661*/662Manifold Manifold::WarpBatch(663std::function<void(VecView<vec3>)> warpFunc) const {664auto oldImpl = GetCsgLeafNode().GetImpl();665if (oldImpl->status_ != Error::NoError) {666auto pImpl = std::make_shared<Impl>();667pImpl->status_ = oldImpl->status_;668return Manifold(std::make_shared<CsgLeafNode>(pImpl));669}670auto pImpl = std::make_shared<Impl>(*oldImpl);671pImpl->WarpBatch(warpFunc);672return Manifold(std::make_shared<CsgLeafNode>(pImpl));673}674675/**676* Create a new copy of this manifold with updated vertex properties by677* supplying a function that takes the existing position and properties as678* input. You may specify any number of output properties, allowing creation and679* removal of channels. Note: undefined behavior will result if you read past680* the number of input properties or write past the number of output properties.681*682* If propFunc is a nullptr, this function will just set the channel to zeroes.683*684* @param numProp The new number of properties per vertex.685* @param propFunc A function that modifies the properties of a given vertex.686*/687Manifold Manifold::SetProperties(688int numProp,689std::function<void(double* newProp, vec3 position, const double* oldProp)>690propFunc) const {691auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());692const int oldNumProp = NumProp();693const Vec<double> oldProperties = pImpl->properties_;694695if (numProp == 0) {696pImpl->properties_.clear();697} else {698pImpl->properties_ = Vec<double>(numProp * NumPropVert(), 0);699for_each_n(700propFunc == nullptr ? ExecutionPolicy::Par : ExecutionPolicy::Seq,701countAt(0), NumTri(), [&](int tri) {702for (int i : {0, 1, 2}) {703const Halfedge& edge = pImpl->halfedge_[3 * tri + i];704const int vert = edge.startVert;705const int propVert = edge.propVert;706if (propFunc == nullptr) {707for (int p = 0; p < numProp; ++p) {708pImpl->properties_[numProp * propVert + p] = 0;709}710} else {711propFunc(&pImpl->properties_[numProp * propVert],712pImpl->vertPos_[vert],713oldProperties.data() + oldNumProp * propVert);714}715}716});717}718719pImpl->numProp_ = numProp;720return Manifold(std::make_shared<CsgLeafNode>(pImpl));721}722723/**724* Curvature is the inverse of the radius of curvature, and signed such that725* positive is convex and negative is concave. There are two orthogonal726* principal curvatures at any point on a manifold, with one maximum and the727* other minimum. Gaussian curvature is their product, while mean728* curvature is their sum. This approximates them for every vertex and assigns729* them as vertex properties on the given channels.730*731* @param gaussianIdx The property channel index in which to store the Gaussian732* curvature. An index < 0 will be ignored (stores nothing). The property set733* will be automatically expanded to include the channel index specified.734*735* @param meanIdx The property channel index in which to store the mean736* curvature. An index < 0 will be ignored (stores nothing). The property set737* will be automatically expanded to include the channel index specified.738*/739Manifold Manifold::CalculateCurvature(int gaussianIdx, int meanIdx) const {740auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());741pImpl->CalculateCurvature(gaussianIdx, meanIdx);742return Manifold(std::make_shared<CsgLeafNode>(pImpl));743}744745/**746* Fills in vertex properties for normal vectors, calculated from the mesh747* geometry. Flat faces composed of three or more triangles will remain flat.748*749* @param normalIdx The property channel in which to store the X750* values of the normals. The X, Y, and Z channels will be sequential. The751* property set will be automatically expanded such that NumProp will be at752* least normalIdx + 3.753*754* @param minSharpAngle Any edges with angles greater than this value will755* remain sharp, getting different normal vector properties on each side of the756* edge. By default, no edges are sharp and all normals are shared. With a value757* of zero, the model is faceted and all normals match their triangle normals,758* but in this case it would be better not to calculate normals at all.759*/760Manifold Manifold::CalculateNormals(int normalIdx, double minSharpAngle) const {761auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());762pImpl->SetNormals(normalIdx, minSharpAngle);763return Manifold(std::make_shared<CsgLeafNode>(pImpl));764}765766/**767* Smooths out the Manifold by filling in the halfedgeTangent vectors. The768* geometry will remain unchanged until Refine or RefineToLength is called to769* interpolate the surface. This version uses the supplied vertex normal770* properties to define the tangent vectors. Faces of two coplanar triangles771* will be marked as quads, while faces with three or more will be flat.772*773* @param normalIdx The first property channel of the normals. NumProp must be774* at least normalIdx + 3. Any vertex where multiple normals exist and don't775* agree will result in a sharp edge.776*/777Manifold Manifold::SmoothByNormals(int normalIdx) const {778auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());779if (!IsEmpty()) {780pImpl->CreateTangents(normalIdx);781}782return Manifold(std::make_shared<CsgLeafNode>(pImpl));783}784785/**786* Smooths out the Manifold by filling in the halfedgeTangent vectors. The787* geometry will remain unchanged until Refine or RefineToLength is called to788* interpolate the surface. This version uses the geometry of the triangles and789* pseudo-normals to define the tangent vectors. Faces of two coplanar triangles790* will be marked as quads.791*792* @param minSharpAngle degrees, default 60. Any edges with angles greater than793* this value will remain sharp. The rest will be smoothed to G1 continuity,794* with the caveat that flat faces of three or more triangles will always remain795* flat. With a value of zero, the model is faceted, but in this case there is796* no point in smoothing.797*798* @param minSmoothness range: 0 - 1, default 0. The smoothness applied to sharp799* angles. The default gives a hard edge, while values > 0 will give a small800* fillet on these sharp edges. A value of 1 is equivalent to a minSharpAngle of801* 180 - all edges will be smooth.802*/803Manifold Manifold::SmoothOut(double minSharpAngle, double minSmoothness) const {804auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());805if (!IsEmpty()) {806if (minSmoothness == 0) {807const int numProp = pImpl->numProp_;808Vec<double> properties = pImpl->properties_;809Vec<Halfedge> halfedge = pImpl->halfedge_;810pImpl->SetNormals(0, minSharpAngle);811pImpl->CreateTangents(0);812// Reset the properties to the original values, removing temporary normals813pImpl->numProp_ = numProp;814pImpl->properties_.swap(properties);815pImpl->halfedge_.swap(halfedge);816} else {817pImpl->CreateTangents(pImpl->SharpenEdges(minSharpAngle, minSmoothness));818}819}820return Manifold(std::make_shared<CsgLeafNode>(pImpl));821}822823/**824* Increase the density of the mesh by splitting every edge into n pieces. For825* instance, with n = 2, each triangle will be split into 4 triangles. Quads826* will ignore their interior triangle bisector. These will all be coplanar (and827* will not be immediately collapsed) unless the Mesh/Manifold has828* halfedgeTangents specified (e.g. from the Smooth() constructor), in which829* case the new vertices will be moved to the interpolated surface according to830* their barycentric coordinates.831*832* @param n The number of pieces to split every edge into. Must be > 1.833*/834Manifold Manifold::Refine(int n) const {835auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());836if (n > 1) {837pImpl->Refine([n](vec3, vec4, vec4) { return n - 1; });838}839return Manifold(std::make_shared<CsgLeafNode>(pImpl));840}841842/**843* Increase the density of the mesh by splitting each edge into pieces of844* roughly the input length. Interior verts are added to keep the rest of the845* triangulation edges also of roughly the same length. If halfedgeTangents are846* present (e.g. from the Smooth() constructor), the new vertices will be moved847* to the interpolated surface according to their barycentric coordinates. Quads848* will ignore their interior triangle bisector.849*850* @param length The length that edges will be broken down to.851*/852Manifold Manifold::RefineToLength(double length) const {853length = std::abs(length);854auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());855pImpl->Refine([length](vec3 edge, vec4, vec4) {856return static_cast<int>(la::length(edge) / length);857});858return Manifold(std::make_shared<CsgLeafNode>(pImpl));859}860861/**862* Increase the density of the mesh by splitting each edge into pieces such that863* any point on the resulting triangles is roughly within tolerance of the864* smoothly curved surface defined by the tangent vectors. This means tightly865* curving regions will be divided more finely than smoother regions. If866* halfedgeTangents are not present, the result will simply be a copy of the867* original. Quads will ignore their interior triangle bisector.868*869* @param tolerance The desired maximum distance between the faceted mesh870* produced and the exact smoothly curving surface. All vertices are exactly on871* the surface, within rounding error.872*/873Manifold Manifold::RefineToTolerance(double tolerance) const {874tolerance = std::abs(tolerance);875auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());876if (!pImpl->halfedgeTangent_.empty()) {877pImpl->Refine(878[tolerance](vec3 edge, vec4 tangentStart, vec4 tangentEnd) {879const vec3 edgeNorm = la::normalize(edge);880// Weight heuristic881const vec3 tStart = vec3(tangentStart);882const vec3 tEnd = vec3(tangentEnd);883// Perpendicular to edge884const vec3 start = tStart - edgeNorm * la::dot(edgeNorm, tStart);885const vec3 end = tEnd - edgeNorm * la::dot(edgeNorm, tEnd);886// Circular arc result plus heuristic term for non-circular curves887const double d = 0.5 * (la::length(start) + la::length(end)) +888la::length(start - end);889return static_cast<int>(std::sqrt(3 * d / (4 * tolerance)));890},891true);892}893return Manifold(std::make_shared<CsgLeafNode>(pImpl));894}895896/**897* The central operation of this library: the Boolean combines two manifolds898* into another by calculating their intersections and removing the unused899* portions.900* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid)901* inputs will produce ε-valid output. ε-invalid input may fail902* triangulation.903*904* These operations are optimized to produce nearly-instant results if either905* input is empty or their bounding boxes do not overlap.906*907* @param second The other Manifold.908* @param op The type of operation to perform.909*/910Manifold Manifold::Boolean(const Manifold& second, OpType op) const {911return Manifold(pNode_->Boolean(second.pNode_, op));912}913914/**915* Perform the given boolean operation on a list of Manifolds. In case of916* Subtract, all Manifolds in the tail are differenced from the head.917*/918Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,919OpType op) {920if (manifolds.size() == 0)921return Manifold();922else if (manifolds.size() == 1)923return manifolds[0];924std::vector<std::shared_ptr<CsgNode>> children;925children.reserve(manifolds.size());926for (const auto& m : manifolds) children.push_back(m.pNode_);927return Manifold(std::make_shared<CsgOpNode>(children, op));928}929930/**931* Shorthand for Boolean Union.932*/933Manifold Manifold::operator+(const Manifold& Q) const {934return Boolean(Q, OpType::Add);935}936937/**938* Shorthand for Boolean Union assignment.939*/940Manifold& Manifold::operator+=(const Manifold& Q) {941*this = *this + Q;942return *this;943}944945/**946* Shorthand for Boolean Difference.947*/948Manifold Manifold::operator-(const Manifold& Q) const {949return Boolean(Q, OpType::Subtract);950}951952/**953* Shorthand for Boolean Difference assignment.954*/955Manifold& Manifold::operator-=(const Manifold& Q) {956*this = *this - Q;957return *this;958}959960/**961* Shorthand for Boolean Intersection.962*/963Manifold Manifold::operator^(const Manifold& Q) const {964return Boolean(Q, OpType::Intersect);965}966967/**968* Shorthand for Boolean Intersection assignment.969*/970Manifold& Manifold::operator^=(const Manifold& Q) {971*this = *this ^ Q;972return *this;973}974975/**976* Split cuts this manifold in two using the cutter manifold. The first result977* is the intersection, second is the difference. This is more efficient than978* doing them separately.979*980* @param cutter981*/982std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {983auto impl1 = GetCsgLeafNode().GetImpl();984auto impl2 = cutter.GetCsgLeafNode().GetImpl();985986Boolean3 boolean(*impl1, *impl2, OpType::Subtract);987auto result1 = std::make_shared<CsgLeafNode>(988std::make_unique<Impl>(boolean.Result(OpType::Intersect)));989auto result2 = std::make_shared<CsgLeafNode>(990std::make_unique<Impl>(boolean.Result(OpType::Subtract)));991return std::make_pair(Manifold(result1), Manifold(result2));992}993994/**995* Convenient version of Split() for a half-space.996*997* @param normal This vector is normal to the cutting plane and its length does998* not matter. The first result is in the direction of this vector, the second999* result is on the opposite side.1000* @param originOffset The distance of the plane from the origin in the1001* direction of the normal vector.1002*/1003std::pair<Manifold, Manifold> Manifold::SplitByPlane(1004vec3 normal, double originOffset) const {1005return Split(Halfspace(BoundingBox(), normal, originOffset));1006}10071008/**1009* Identical to SplitByPlane(), but calculating and returning only the first1010* result.1011*1012* @param normal This vector is normal to the cutting plane and its length does1013* not matter. The result is in the direction of this vector from the plane.1014* @param originOffset The distance of the plane from the origin in the1015* direction of the normal vector.1016*/1017Manifold Manifold::TrimByPlane(vec3 normal, double originOffset) const {1018return *this ^ Halfspace(BoundingBox(), normal, originOffset);1019}10201021/**1022* Returns the cross section of this object parallel to the X-Y plane at the1023* specified Z height, defaulting to zero. Using a height equal to the bottom of1024* the bounding box will return the bottom faces, while using a height equal to1025* the top of the bounding box will return empty.1026*/1027Polygons Manifold::Slice(double height) const {1028return GetCsgLeafNode().GetImpl()->Slice(height);1029}10301031/**1032* Returns polygons representing the projected outline of this object1033* onto the X-Y plane. These polygons will often self-intersect, so it is1034* recommended to run them through the positive fill rule of CrossSection to get1035* a sensible result before using them.1036*/1037Polygons Manifold::Project() const {1038return GetCsgLeafNode().GetImpl()->Project();1039}10401041ExecutionParams& ManifoldParams() { return manifoldParams; }10421043/**1044* Compute the convex hull of a set of points. If the given points are fewer1045* than 4, or they are all coplanar, an empty Manifold will be returned.1046*1047* @param pts A vector of 3-dimensional points over which to compute a convex1048* hull.1049*/1050Manifold Manifold::Hull(const std::vector<vec3>& pts) {1051std::shared_ptr<Impl> impl = std::make_shared<Impl>();1052impl->Hull(Vec<vec3>(pts));1053return Manifold(std::make_shared<CsgLeafNode>(impl));1054}10551056/**1057* Compute the convex hull of this manifold.1058*/1059Manifold Manifold::Hull() const {1060std::shared_ptr<Impl> impl = std::make_shared<Impl>();1061impl->Hull(GetCsgLeafNode().GetImpl()->vertPos_);1062return Manifold(std::make_shared<CsgLeafNode>(impl));1063}10641065/**1066* Compute the convex hull enveloping a set of manifolds.1067*1068* @param manifolds A vector of manifolds over which to compute a convex hull.1069*/1070Manifold Manifold::Hull(const std::vector<Manifold>& manifolds) {1071return Compose(manifolds).Hull();1072}10731074/**1075* Returns the minimum gap between two manifolds. Returns a double between1076* 0 and searchLength.1077*1078* @param other The other manifold to compute the minimum gap to.1079* @param searchLength The maximum distance to search for a minimum gap.1080*/1081double Manifold::MinGap(const Manifold& other, double searchLength) const {1082auto intersect = *this ^ other;1083if (!intersect.IsEmpty()) return 0.0;10841085return GetCsgLeafNode().GetImpl()->MinGap(*other.GetCsgLeafNode().GetImpl(),1086searchLength);1087}1088} // namespace manifold108910901091