Path: blob/master/thirdparty/manifold/src/constructors.cpp
9903 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 "csg_tree.h"15#include "disjoint_sets.h"16#include "impl.h"17#include "manifold/manifold.h"18#include "manifold/polygon.h"19#include "parallel.h"2021namespace {22using namespace manifold;2324template <typename P, typename I>25std::shared_ptr<Manifold::Impl> SmoothImpl(26const MeshGLP<P, I>& meshGL,27const std::vector<Smoothness>& sharpenedEdges) {28DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,29"when supplying tangents, the normal constructor should be used "30"rather than Smooth().");3132MeshGLP<P, I> meshTmp = meshGL;33meshTmp.faceID.resize(meshGL.NumTri());34std::iota(meshTmp.faceID.begin(), meshTmp.faceID.end(), 0);3536std::shared_ptr<Manifold::Impl> impl =37std::make_shared<Manifold::Impl>(meshTmp);38impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));39// Restore the original faceID40const size_t numTri = impl->NumTri();41for (size_t i = 0; i < numTri; ++i) {42if (meshGL.faceID.size() == numTri) {43impl->meshRelation_.triRef[i].faceID =44meshGL.faceID[impl->meshRelation_.triRef[i].faceID];45} else {46impl->meshRelation_.triRef[i].faceID = -1;47}48}49return impl;50}51} // namespace5253namespace manifold {54/**55* Constructs a smooth version of the input mesh by creating tangents; this56* method will throw if you have supplied tangents with your mesh already. The57* actual triangle resolution is unchanged; use the Refine() method to58* interpolate to a higher-resolution curve.59*60* By default, every edge is calculated for maximum smoothness (very much61* approximately), attempting to minimize the maximum mean Curvature magnitude.62* No higher-order derivatives are considered, as the interpolation is63* independent per triangle, only sharing constraints on their boundaries.64*65* @param meshGL input MeshGL.66* @param sharpenedEdges If desired, you can supply a vector of sharpened67* halfedges, which should in general be a small subset of all halfedges. Order68* of entries doesn't matter, as each one specifies the desired smoothness69* (between zero and one, with one the default for all unspecified halfedges)70* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge71* between triVert 0 and 1, etc).72*73* At a smoothness value of zero, a sharp crease is made. The smoothness is74* interpolated along each edge, so the specified value should be thought of as75* an average. Where exactly two sharpened edges meet at a vertex, their76* tangents are rotated to be colinear so that the sharpened edge can be77* continuous. Vertices with only one sharpened edge are completely smooth,78* allowing sharpened edges to smoothly vanish at termination. A single vertex79* can be sharpened by sharping all edges that are incident on it, allowing80* cones to be formed.81*/82Manifold Manifold::Smooth(const MeshGL& meshGL,83const std::vector<Smoothness>& sharpenedEdges) {84return Manifold(SmoothImpl(meshGL, sharpenedEdges));85}8687/**88* Constructs a smooth version of the input mesh by creating tangents; this89* method will throw if you have supplied tangents with your mesh already. The90* actual triangle resolution is unchanged; use the Refine() method to91* interpolate to a higher-resolution curve.92*93* By default, every edge is calculated for maximum smoothness (very much94* approximately), attempting to minimize the maximum mean Curvature magnitude.95* No higher-order derivatives are considered, as the interpolation is96* independent per triangle, only sharing constraints on their boundaries.97*98* @param meshGL64 input MeshGL64.99* @param sharpenedEdges If desired, you can supply a vector of sharpened100* halfedges, which should in general be a small subset of all halfedges. Order101* of entries doesn't matter, as each one specifies the desired smoothness102* (between zero and one, with one the default for all unspecified halfedges)103* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge104* between triVert 0 and 1, etc).105*106* At a smoothness value of zero, a sharp crease is made. The smoothness is107* interpolated along each edge, so the specified value should be thought of as108* an average. Where exactly two sharpened edges meet at a vertex, their109* tangents are rotated to be colinear so that the sharpened edge can be110* continuous. Vertices with only one sharpened edge are completely smooth,111* allowing sharpened edges to smoothly vanish at termination. A single vertex112* can be sharpened by sharping all edges that are incident on it, allowing113* cones to be formed.114*/115Manifold Manifold::Smooth(const MeshGL64& meshGL64,116const std::vector<Smoothness>& sharpenedEdges) {117return Manifold(SmoothImpl(meshGL64, sharpenedEdges));118}119120/**121* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)122* and the rest at similarly symmetric points.123*/124Manifold Manifold::Tetrahedron() {125return Manifold(std::make_shared<Impl>(Impl::Shape::Tetrahedron));126}127128/**129* Constructs a unit cube (edge lengths all one), by default in the first130* octant, touching the origin. If any dimensions in size are negative, or if131* all are zero, an empty Manifold will be returned.132*133* @param size The X, Y, and Z dimensions of the box.134* @param center Set to true to shift the center to the origin.135*/136Manifold Manifold::Cube(vec3 size, bool center) {137if (size.x < 0.0 || size.y < 0.0 || size.z < 0.0 || la::length(size) == 0.) {138return Invalid();139}140mat3x4 m({{size.x, 0.0, 0.0}, {0.0, size.y, 0.0}, {0.0, 0.0, size.z}},141center ? (-size / 2.0) : vec3(0.0));142return Manifold(std::make_shared<Impl>(Manifold::Impl::Shape::Cube, m));143}144145/**146* A convenience constructor for the common case of extruding a circle. Can also147* form cones if both radii are specified.148*149* @param height Z-extent150* @param radiusLow Radius of bottom circle. Must be positive.151* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to152* radiusLow.153* @param circularSegments How many line segments to use around the circle.154* Default is calculated by the static Defaults.155* @param center Set to true to shift the center to the origin. Default is156* origin at the bottom.157*/158Manifold Manifold::Cylinder(double height, double radiusLow, double radiusHigh,159int circularSegments, bool center) {160if (height <= 0.0 || radiusLow <= 0.0) {161return Invalid();162}163const double scale = radiusHigh >= 0.0 ? radiusHigh / radiusLow : 1.0;164const double radius = fmax(radiusLow, radiusHigh);165const int n = circularSegments > 2 ? circularSegments166: Quality::GetCircularSegments(radius);167168SimplePolygon circle(n);169const double dPhi = 360.0 / n;170for (int i = 0; i < n; ++i) {171circle[i] = {radiusLow * cosd(dPhi * i), radiusLow * sind(dPhi * i)};172}173174Manifold cylinder = Manifold::Extrude({circle}, height, 0, 0.0, vec2(scale));175if (center)176cylinder = cylinder.Translate(vec3(0.0, 0.0, -height / 2.0)).AsOriginal();177return cylinder;178}179180/**181* Constructs a geodesic sphere of a given radius.182*183* @param radius Radius of the sphere. Must be positive.184* @param circularSegments Number of segments along its185* diameter. This number will always be rounded up to the nearest factor of186* four, as this sphere is constructed by refining an octahedron. This means187* there are a circle of vertices on all three of the axis planes. Default is188* calculated by the static Defaults.189*/190Manifold Manifold::Sphere(double radius, int circularSegments) {191if (radius <= 0.0) {192return Invalid();193}194int n = circularSegments > 0 ? (circularSegments + 3) / 4195: Quality::GetCircularSegments(radius) / 4;196auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);197pImpl_->Subdivide([n](vec3, vec4, vec4) { return n - 1; });198for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(),199pImpl_->NumVert(), [radius](vec3& v) {200v = la::cos(kHalfPi * (1.0 - v));201v = radius * la::normalize(v);202if (std::isnan(v.x)) v = vec3(0.0);203});204pImpl_->Finish();205// Ignore preceding octahedron.206pImpl_->InitializeOriginal();207return Manifold(pImpl_);208}209210/**211* Constructs a manifold from a set of polygons by extruding them along the212* Z-axis.213* Note that high twistDegrees with small nDivisions may cause214* self-intersection. This is not checked here and it is up to the user to215* choose the correct parameters.216*217* @param crossSection A set of non-overlapping polygons to extrude.218* @param height Z-extent of extrusion.219* @param nDivisions Number of extra copies of the crossSection to insert into220* the shape vertically; especially useful in combination with twistDegrees to221* avoid interpolation artifacts. Default is none.222* @param twistDegrees Amount to twist the top crossSection relative to the223* bottom, interpolated linearly for the divisions in between.224* @param scaleTop Amount to scale the top (independently in X and Y). If the225* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.226* Note that scale is applied after twist.227* Default {1, 1}.228*/229Manifold Manifold::Extrude(const Polygons& crossSection, double height,230int nDivisions, double twistDegrees, vec2 scaleTop) {231ZoneScoped;232if (crossSection.size() == 0 || height <= 0.0) {233return Invalid();234}235236scaleTop.x = std::max(scaleTop.x, 0.0);237scaleTop.y = std::max(scaleTop.y, 0.0);238239auto pImpl_ = std::make_shared<Impl>();240++nDivisions;241auto& vertPos = pImpl_->vertPos_;242Vec<ivec3> triVertsDH;243auto& triVerts = triVertsDH;244int nCrossSection = 0;245bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;246size_t idx = 0;247PolygonsIdx polygonsIndexed;248for (auto& poly : crossSection) {249nCrossSection += poly.size();250SimplePolygonIdx simpleIndexed;251for (const vec2& polyVert : poly) {252vertPos.push_back({polyVert.x, polyVert.y, 0.0});253simpleIndexed.push_back({polyVert, static_cast<int>(idx++)});254}255polygonsIndexed.push_back(simpleIndexed);256}257for (int i = 1; i < nDivisions + 1; ++i) {258double alpha = i / double(nDivisions);259double phi = alpha * twistDegrees;260vec2 scale = la::lerp(vec2(1.0), scaleTop, alpha);261mat2 rotation({cosd(phi), sind(phi)}, {-sind(phi), cosd(phi)});262mat2 transform = mat2({scale.x, 0.0}, {0.0, scale.y}) * rotation;263size_t j = 0;264size_t idx = 0;265for (const auto& poly : crossSection) {266for (size_t vert = 0; vert < poly.size(); ++vert) {267size_t offset = idx + nCrossSection * i;268size_t thisVert = vert + offset;269size_t lastVert = (vert == 0 ? poly.size() : vert) - 1 + offset;270if (i == nDivisions && isCone) {271triVerts.push_back(ivec3(nCrossSection * i + j,272lastVert - nCrossSection,273thisVert - nCrossSection));274} else {275vec2 pos = transform * poly[vert];276vertPos.push_back({pos.x, pos.y, height * alpha});277triVerts.push_back(278ivec3(thisVert, lastVert, thisVert - nCrossSection));279triVerts.push_back(ivec3(lastVert, lastVert - nCrossSection,280thisVert - nCrossSection));281}282}283++j;284idx += poly.size();285}286}287if (isCone)288for (size_t j = 0; j < crossSection.size();289++j) // Duplicate vertex for Genus290vertPos.push_back({0.0, 0.0, height});291std::vector<ivec3> top = TriangulateIdx(polygonsIndexed);292for (const ivec3& tri : top) {293triVerts.push_back({tri[0], tri[2], tri[1]});294if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);295}296297pImpl_->CreateHalfedges(triVertsDH);298pImpl_->Finish();299pImpl_->InitializeOriginal();300pImpl_->MarkCoplanar();301return Manifold(pImpl_);302}303304/**305* Constructs a manifold from a set of polygons by revolving this cross-section306* around its Y-axis and then setting this as the Z-axis of the resulting307* manifold. If the polygons cross the Y-axis, only the part on the positive X308* side is used. Geometrically valid input will result in geometrically valid309* output.310*311* @param crossSection A set of non-overlapping polygons to revolve.312* @param circularSegments Number of segments along its diameter. Default is313* calculated by the static Defaults.314* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.315*/316Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments,317double revolveDegrees) {318ZoneScoped;319320Polygons polygons;321double radius = 0;322for (const SimplePolygon& poly : crossSection) {323size_t i = 0;324while (i < poly.size() && poly[i].x < 0) {325++i;326}327if (i == poly.size()) {328continue;329}330polygons.push_back({});331const size_t start = i;332do {333if (poly[i].x >= 0) {334polygons.back().push_back(poly[i]);335radius = std::max(radius, poly[i].x);336}337const size_t next = i + 1 == poly.size() ? 0 : i + 1;338if ((poly[next].x < 0) != (poly[i].x < 0)) {339const double y = poly[next].y - poly[next].x *340(poly[i].y - poly[next].y) /341(poly[i].x - poly[next].x);342polygons.back().push_back({0, y});343}344i = next;345} while (i != start);346}347348if (polygons.empty()) {349return Invalid();350}351352if (revolveDegrees > 360.0) {353revolveDegrees = 360.0;354}355const bool isFullRevolution = revolveDegrees == 360.0;356357const int nDivisions =358circularSegments > 2359? circularSegments360: Quality::GetCircularSegments(radius) * revolveDegrees / 360;361362auto pImpl_ = std::make_shared<Impl>();363auto& vertPos = pImpl_->vertPos_;364Vec<ivec3> triVertsDH;365auto& triVerts = triVertsDH;366367std::vector<int> startPoses;368std::vector<int> endPoses;369370const double dPhi = revolveDegrees / nDivisions;371// first and last slice are distinguished if not a full revolution.372const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1;373374for (const auto& poly : polygons) {375size_t nPosVerts = 0;376size_t nRevolveAxisVerts = 0;377for (auto& pt : poly) {378if (pt.x > 0) {379nPosVerts++;380} else {381nRevolveAxisVerts++;382}383}384385for (size_t polyVert = 0; polyVert < poly.size(); ++polyVert) {386const size_t startPosIndex = vertPos.size();387388if (!isFullRevolution) startPoses.push_back(startPosIndex);389390const vec2 currPolyVertex = poly[polyVert];391const vec2 prevPolyVertex =392poly[polyVert == 0 ? poly.size() - 1 : polyVert - 1];393394const int prevStartPosIndex =395startPosIndex +396(polyVert == 0 ? nRevolveAxisVerts + (nSlices * nPosVerts) : 0) +397(prevPolyVertex.x == 0.0 ? -1 : -nSlices);398399for (int slice = 0; slice < nSlices; ++slice) {400const double phi = slice * dPhi;401if (slice == 0 || currPolyVertex.x > 0) {402vertPos.push_back({currPolyVertex.x * cosd(phi),403currPolyVertex.x * sind(phi), currPolyVertex.y});404}405406if (isFullRevolution || slice > 0) {407const int lastSlice = (slice == 0 ? nDivisions : slice) - 1;408if (currPolyVertex.x > 0.0) {409triVerts.push_back(ivec3(410startPosIndex + slice, startPosIndex + lastSlice,411// "Reuse" vertex of first slice if it lies on the revolve axis412(prevPolyVertex.x == 0.0 ? prevStartPosIndex413: prevStartPosIndex + lastSlice)));414}415416if (prevPolyVertex.x > 0.0) {417triVerts.push_back(418ivec3(prevStartPosIndex + lastSlice, prevStartPosIndex + slice,419(currPolyVertex.x == 0.0 ? startPosIndex420: startPosIndex + slice)));421}422}423}424if (!isFullRevolution) endPoses.push_back(vertPos.size() - 1);425}426}427428// Add front and back triangles if not a full revolution.429if (!isFullRevolution) {430std::vector<ivec3> frontTriangles = Triangulate(polygons, pImpl_->epsilon_);431for (auto& t : frontTriangles) {432triVerts.push_back({startPoses[t.x], startPoses[t.y], startPoses[t.z]});433}434435for (auto& t : frontTriangles) {436triVerts.push_back({endPoses[t.z], endPoses[t.y], endPoses[t.x]});437}438}439440pImpl_->CreateHalfedges(triVertsDH);441pImpl_->Finish();442pImpl_->InitializeOriginal();443pImpl_->MarkCoplanar();444return Manifold(pImpl_);445}446447/**448* Constructs a new manifold from a vector of other manifolds. This is a purely449* topological operation, so care should be taken to avoid creating450* overlapping results. It is the inverse operation of Decompose().451*452* @param manifolds A vector of Manifolds to lazy-union together.453*/454Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {455std::vector<std::shared_ptr<CsgLeafNode>> children;456for (const auto& manifold : manifolds) {457children.push_back(manifold.pNode_->ToLeafNode());458}459return Manifold(CsgLeafNode::Compose(children));460}461462/**463* This operation returns a vector of Manifolds that are topologically464* disconnected. If everything is connected, the vector is length one,465* containing a copy of the original. It is the inverse operation of Compose().466*/467std::vector<Manifold> Manifold::Decompose() const {468ZoneScoped;469DisjointSets uf(NumVert());470// Graph graph;471auto pImpl_ = GetCsgLeafNode().GetImpl();472for (const Halfedge& halfedge : pImpl_->halfedge_) {473if (halfedge.IsForward()) uf.unite(halfedge.startVert, halfedge.endVert);474}475std::vector<int> componentIndices;476const int numComponents = uf.connectedComponents(componentIndices);477478if (numComponents == 1) {479std::vector<Manifold> meshes(1);480meshes[0] = *this;481return meshes;482}483Vec<int> vertLabel(componentIndices);484485const int numVert = NumVert();486std::vector<Manifold> meshes;487for (int i = 0; i < numComponents; ++i) {488auto impl = std::make_shared<Impl>();489// inherit original object's precision490impl->epsilon_ = pImpl_->epsilon_;491impl->tolerance_ = pImpl_->tolerance_;492493Vec<int> vertNew2Old(numVert);494const int nVert =495copy_if(countAt(0), countAt(numVert), vertNew2Old.begin(),496[i, &vertLabel](int v) { return vertLabel[v] == i; }) -497vertNew2Old.begin();498impl->vertPos_.resize(nVert);499vertNew2Old.resize(nVert);500gather(vertNew2Old.begin(), vertNew2Old.end(), pImpl_->vertPos_.begin(),501impl->vertPos_.begin());502503Vec<int> faceNew2Old(NumTri());504const auto& halfedge = pImpl_->halfedge_;505const int nFace =506copy_if(countAt(0_uz), countAt(NumTri()), faceNew2Old.begin(),507[i, &vertLabel, &halfedge](int face) {508return vertLabel[halfedge[3 * face].startVert] == i;509}) -510faceNew2Old.begin();511512if (nFace == 0) continue;513faceNew2Old.resize(nFace);514515impl->GatherFaces(*pImpl_, faceNew2Old);516impl->ReindexVerts(vertNew2Old, pImpl_->NumVert());517impl->Finish();518519meshes.push_back(Manifold(impl));520}521return meshes;522}523} // namespace manifold524525526