// 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 #include #include #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 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 MeshGLP 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 out; out.numProp = 3 + numProp; out.tolerance = impl.tolerance_; if (std::is_same::value) out.tolerance = std::max(out.tolerance, static_cast(std::numeric_limits::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 triNew2Old(numTri); std::iota(triNew2Old.begin(), triNew2Old.end(), 0); VecView 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 runNormalTransform; auto addRun = [updateNormals, isOriginal]( MeshGLP& out, std::vector& 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 vert2idx(impl.NumVert(), -1); std::vector> vertPropPair(impl.NumVert()); out.vertProperties.reserve(numVert * static_cast(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()} {} 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 pNode) : pNode_(pNode) {} Manifold::Manifold(std::shared_ptr pImpl_) : pNode_(std::make_shared(pImpl_)) {} Manifold Manifold::Invalid() { auto pImpl_ = std::make_shared(); 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(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(std::make_shared(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(std::make_shared(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(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(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(*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(); newImpl->status_ = oldImpl->status_; return Manifold(std::make_shared(newImpl)); } auto newImpl = std::make_shared(*oldImpl); newImpl->InitializeOriginal(); newImpl->CreateFaces(); newImpl->SimplifyTopology(); newImpl->Finish(); newImpl->InitializeOriginal(true); return Manifold(std::make_shared(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 warpFunc) const { auto oldImpl = GetCsgLeafNode().GetImpl(); if (oldImpl->status_ != Error::NoError) { auto pImpl = std::make_shared(); pImpl->status_ = oldImpl->status_; return Manifold(std::make_shared(pImpl)); } auto pImpl = std::make_shared(*oldImpl); pImpl->Warp(warpFunc); return Manifold(std::make_shared(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)> warpFunc) const { auto oldImpl = GetCsgLeafNode().GetImpl(); if (oldImpl->status_ != Error::NoError) { auto pImpl = std::make_shared(); pImpl->status_ = oldImpl->status_; return Manifold(std::make_shared(pImpl)); } auto pImpl = std::make_shared(*oldImpl); pImpl->WarpBatch(warpFunc); return Manifold(std::make_shared(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 propFunc) const { auto pImpl = std::make_shared(*GetCsgLeafNode().GetImpl()); const int oldNumProp = NumProp(); const Vec 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(numProp * NumVert(), 0); } else { pImpl->meshRelation_.properties = Vec(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(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(*GetCsgLeafNode().GetImpl()); pImpl->CalculateCurvature(gaussianIdx, meanIdx); return Manifold(std::make_shared(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(*GetCsgLeafNode().GetImpl()); pImpl->SetNormals(normalIdx, minSharpAngle); return Manifold(std::make_shared(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(*GetCsgLeafNode().GetImpl()); if (!IsEmpty()) { pImpl->CreateTangents(normalIdx); } return Manifold(std::make_shared(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(*GetCsgLeafNode().GetImpl()); if (!IsEmpty()) { if (minSmoothness == 0) { const int numProp = pImpl->meshRelation_.numProp; Vec properties = pImpl->meshRelation_.properties; Vec 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(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(*GetCsgLeafNode().GetImpl()); if (n > 1) { pImpl->Refine( [n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; }); } return Manifold(std::make_shared(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(*GetCsgLeafNode().GetImpl()); pImpl->Refine([length](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return static_cast(la::length(edge) / length); }); return Manifold(std::make_shared(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(*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(std::sqrt(3 * d / (4 * tolerance))); }, true); } return Manifold(std::make_shared(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& manifolds, OpType op) { if (manifolds.size() == 0) return Manifold(); else if (manifolds.size() == 1) return manifolds[0]; std::vector> children; children.reserve(manifolds.size()); for (const auto& m : manifolds) children.push_back(m.pNode_); return Manifold(std::make_shared(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::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( std::make_unique(boolean.Result(OpType::Intersect))); auto result2 = std::make_shared( std::make_unique(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::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& pts) { std::shared_ptr impl = std::make_shared(); impl->Hull(Vec(pts)); return Manifold(std::make_shared(impl)); } /** * Compute the convex hull of this manifold. */ Manifold Manifold::Hull() const { std::shared_ptr impl = std::make_shared(); impl->Hull(GetCsgLeafNode().GetImpl()->vertPos_); return Manifold(std::make_shared(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& 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