Path: blob/master/thirdparty/manifold/src/smoothing.cpp
9902 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 "impl.h"15#include "parallel.h"1617namespace {18using namespace manifold;1920// Returns a normalized vector orthogonal to ref, in the plane of ref and in,21// unless in and ref are colinear, in which case it falls back to the plane of22// ref and altIn.23vec3 OrthogonalTo(vec3 in, vec3 altIn, vec3 ref) {24vec3 out = in - la::dot(in, ref) * ref;25if (la::dot(out, out) < kPrecision * la::dot(in, in)) {26out = altIn - la::dot(altIn, ref) * ref;27}28return SafeNormalize(out);29}3031double Wrap(double radians) {32return radians < -kPi ? radians + kTwoPi33: radians > kPi ? radians - kTwoPi34: radians;35}3637// Get the angle between two unit-vectors.38double AngleBetween(vec3 a, vec3 b) {39const double dot = la::dot(a, b);40return dot >= 1 ? 0 : (dot <= -1 ? kPi : la::acos(dot));41}4243// Calculate a tangent vector in the form of a weighted cubic Bezier taking as44// input the desired tangent direction (length doesn't matter) and the edge45// vector to the neighboring vertex. In a symmetric situation where the tangents46// at each end are mirror images of each other, this will result in a circular47// arc.48vec4 CircularTangent(const vec3& tangent, const vec3& edgeVec) {49const vec3 dir = SafeNormalize(tangent);5051double weight = std::max(0.5, la::dot(dir, SafeNormalize(edgeVec)));52// Quadratic weighted bezier for circular interpolation53const vec4 bz2 = vec4(dir * 0.5 * la::length(edgeVec), weight);54// Equivalent cubic weighted bezier55const vec4 bz3 = la::lerp(vec4(0, 0, 0, 1), bz2, 2 / 3.0);56// Convert from homogeneous form to geometric form57return vec4(vec3(bz3) / bz3.w, bz3.w);58}5960struct InterpTri {61VecView<vec3> vertPos;62VecView<const Barycentric> vertBary;63const Manifold::Impl* impl;6465static vec4 Homogeneous(vec4 v) {66v.x *= v.w;67v.y *= v.w;68v.z *= v.w;69return v;70}7172static vec4 Homogeneous(vec3 v) { return vec4(v, 1.0); }7374static vec3 HNormalize(vec4 v) {75return v.w == 0 ? vec3(v) : (vec3(v) / v.w);76}7778static vec4 Scale(vec4 v, double scale) { return vec4(scale * vec3(v), v.w); }7980static vec4 Bezier(vec3 point, vec4 tangent) {81return Homogeneous(vec4(point, 0) + tangent);82}8384static mat4x2 CubicBezier2Linear(vec4 p0, vec4 p1, vec4 p2, vec4 p3,85double x) {86mat4x2 out;87vec4 p12 = la::lerp(p1, p2, x);88out[0] = la::lerp(la::lerp(p0, p1, x), p12, x);89out[1] = la::lerp(p12, la::lerp(p2, p3, x), x);90return out;91}9293static vec3 BezierPoint(mat4x2 points, double x) {94return HNormalize(la::lerp(points[0], points[1], x));95}9697static vec3 BezierTangent(mat4x2 points) {98return SafeNormalize(HNormalize(points[1]) - HNormalize(points[0]));99}100101static vec3 RotateFromTo(vec3 v, quat start, quat end) {102return la::qrot(end, la::qrot(la::qconj(start), v));103}104105static quat Slerp(const quat& x, const quat& y, double a, bool longWay) {106quat z = y;107double cosTheta = la::dot(x, y);108109// Take the long way around the sphere only when requested110if ((cosTheta < 0) != longWay) {111z = -y;112cosTheta = -cosTheta;113}114115if (cosTheta > 1.0 - std::numeric_limits<double>::epsilon()) {116return la::lerp(x, z, a); // for numerical stability117} else {118double angle = std::acos(cosTheta);119return (std::sin((1.0 - a) * angle) * x + std::sin(a * angle) * z) /120std::sin(angle);121}122}123124static mat4x2 Bezier2Bezier(const mat3x2& corners, const mat4x2& tangentsX,125const mat4x2& tangentsY, double x,126const vec3& anchor) {127const mat4x2 bez = CubicBezier2Linear(128Homogeneous(corners[0]), Bezier(corners[0], tangentsX[0]),129Bezier(corners[1], tangentsX[1]), Homogeneous(corners[1]), x);130const vec3 end = BezierPoint(bez, x);131const vec3 tangent = BezierTangent(bez);132133const mat3x2 nTangentsX(SafeNormalize(vec3(tangentsX[0])),134-SafeNormalize(vec3(tangentsX[1])));135const mat3x2 biTangents = {136OrthogonalTo(vec3(tangentsY[0]), (anchor - corners[0]), nTangentsX[0]),137OrthogonalTo(vec3(tangentsY[1]), (anchor - corners[1]), nTangentsX[1])};138139const quat q0 = la::rotation_quat(mat3(140nTangentsX[0], biTangents[0], la::cross(nTangentsX[0], biTangents[0])));141const quat q1 = la::rotation_quat(mat3(142nTangentsX[1], biTangents[1], la::cross(nTangentsX[1], biTangents[1])));143const vec3 edge = corners[1] - corners[0];144const bool longWay =145la::dot(nTangentsX[0], edge) + la::dot(nTangentsX[1], edge) < 0;146const quat qTmp = Slerp(q0, q1, x, longWay);147const quat q = la::qmul(la::rotation_quat(la::qxdir(qTmp), tangent), qTmp);148149const vec3 delta = la::lerp(RotateFromTo(vec3(tangentsY[0]), q0, q),150RotateFromTo(vec3(tangentsY[1]), q1, q), x);151const double deltaW = la::lerp(tangentsY[0].w, tangentsY[1].w, x);152153return {Homogeneous(end), vec4(delta, deltaW)};154}155156static vec3 Bezier2D(const mat3x4& corners, const mat4& tangentsX,157const mat4& tangentsY, double x, double y,158const vec3& centroid) {159mat4x2 bez0 =160Bezier2Bezier({corners[0], corners[1]}, {tangentsX[0], tangentsX[1]},161{tangentsY[0], tangentsY[1]}, x, centroid);162mat4x2 bez1 =163Bezier2Bezier({corners[2], corners[3]}, {tangentsX[2], tangentsX[3]},164{tangentsY[2], tangentsY[3]}, 1 - x, centroid);165166const mat4x2 bez =167CubicBezier2Linear(bez0[0], Bezier(vec3(bez0[0]), bez0[1]),168Bezier(vec3(bez1[0]), bez1[1]), bez1[0], y);169return BezierPoint(bez, y);170}171172void operator()(const int vert) {173vec3& pos = vertPos[vert];174const int tri = vertBary[vert].tri;175const vec4 uvw = vertBary[vert].uvw;176177const ivec4 halfedges = impl->GetHalfedges(tri);178const mat3x4 corners = {179impl->vertPos_[impl->halfedge_[halfedges[0]].startVert],180impl->vertPos_[impl->halfedge_[halfedges[1]].startVert],181impl->vertPos_[impl->halfedge_[halfedges[2]].startVert],182halfedges[3] < 0183? vec3(0.0)184: impl->vertPos_[impl->halfedge_[halfedges[3]].startVert]};185186for (const int i : {0, 1, 2, 3}) {187if (uvw[i] == 1) {188pos = corners[i];189return;190}191}192193vec4 posH(0.0);194195if (halfedges[3] < 0) { // tri196const mat4x3 tangentR = {impl->halfedgeTangent_[halfedges[0]],197impl->halfedgeTangent_[halfedges[1]],198impl->halfedgeTangent_[halfedges[2]]};199const mat4x3 tangentL = {200impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge],201impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],202impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge]};203const vec3 centroid = mat3(corners) * vec3(1.0 / 3);204205for (const int i : {0, 1, 2}) {206const int j = Next3(i);207const int k = Prev3(i);208const double x = uvw[k] / (1 - uvw[i]);209210const mat4x2 bez =211Bezier2Bezier({corners[j], corners[k]}, {tangentR[j], tangentL[k]},212{tangentL[j], tangentR[k]}, x, centroid);213214const mat4x2 bez1 = CubicBezier2Linear(215bez[0], Bezier(vec3(bez[0]), bez[1]),216Bezier(corners[i], la::lerp(tangentR[i], tangentL[i], x)),217Homogeneous(corners[i]), uvw[i]);218const vec3 p = BezierPoint(bez1, uvw[i]);219posH += Homogeneous(vec4(p, uvw[j] * uvw[k]));220}221} else { // quad222const mat4 tangentsX = {223impl->halfedgeTangent_[halfedges[0]],224impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],225impl->halfedgeTangent_[halfedges[2]],226impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge]};227const mat4 tangentsY = {228impl->halfedgeTangent_[impl->halfedge_[halfedges[3]].pairedHalfedge],229impl->halfedgeTangent_[halfedges[1]],230impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge],231impl->halfedgeTangent_[halfedges[3]]};232const vec3 centroid = corners * vec4(0.25);233const double x = uvw[1] + uvw[2];234const double y = uvw[2] + uvw[3];235const vec3 pX = Bezier2D(corners, tangentsX, tangentsY, x, y, centroid);236const vec3 pY =237Bezier2D({corners[1], corners[2], corners[3], corners[0]},238{tangentsY[1], tangentsY[2], tangentsY[3], tangentsY[0]},239{tangentsX[1], tangentsX[2], tangentsX[3], tangentsX[0]}, y,2401 - x, centroid);241posH += Homogeneous(vec4(pX, x * (1 - x)));242posH += Homogeneous(vec4(pY, y * (1 - y)));243}244pos = HNormalize(posH);245}246};247} // namespace248249namespace manifold {250251/**252* Get the property normal associated with the startVert of this halfedge, where253* normalIdx shows the beginning of where normals are stored in the properties.254*/255vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const {256const int prop = halfedge_[halfedge].propVert;257vec3 normal;258for (const int i : {0, 1, 2}) {259normal[i] = properties_[prop * numProp_ + normalIdx + i];260}261return normal;262}263264/**265* Returns a circular tangent for the requested halfedge, orthogonal to the266* given normal vector, and avoiding folding.267*/268vec4 Manifold::Impl::TangentFromNormal(const vec3& normal, int halfedge) const {269const Halfedge edge = halfedge_[halfedge];270const vec3 edgeVec = vertPos_[edge.endVert] - vertPos_[edge.startVert];271const vec3 edgeNormal =272faceNormal_[halfedge / 3] + faceNormal_[edge.pairedHalfedge / 3];273vec3 dir = la::cross(la::cross(edgeNormal, edgeVec), normal);274return CircularTangent(dir, edgeVec);275}276277/**278* Returns true if this halfedge should be marked as the interior of a quad, as279* defined by its two triangles referring to the same face, and those triangles280* having no further face neighbors beyond.281*/282bool Manifold::Impl::IsInsideQuad(int halfedge) const {283if (halfedgeTangent_.size() > 0) {284return halfedgeTangent_[halfedge].w < 0;285}286const int tri = halfedge / 3;287const TriRef ref = meshRelation_.triRef[tri];288const int pair = halfedge_[halfedge].pairedHalfedge;289const int pairTri = pair / 3;290const TriRef pairRef = meshRelation_.triRef[pairTri];291if (!ref.SameFace(pairRef)) return false;292293auto SameFace = [this](int halfedge, const TriRef& ref) {294return ref.SameFace(295meshRelation_.triRef[halfedge_[halfedge].pairedHalfedge / 3]);296};297298int neighbor = NextHalfedge(halfedge);299if (SameFace(neighbor, ref)) return false;300neighbor = NextHalfedge(neighbor);301if (SameFace(neighbor, ref)) return false;302neighbor = NextHalfedge(pair);303if (SameFace(neighbor, pairRef)) return false;304neighbor = NextHalfedge(neighbor);305if (SameFace(neighbor, pairRef)) return false;306return true;307}308309/**310* Returns true if this halfedge is an interior of a quad, as defined by its311* halfedge tangent having negative weight.312*/313bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const {314return halfedgeTangent_.size() > 0 && halfedgeTangent_[halfedge].w < 0;315}316317// sharpenedEdges are referenced to the input Mesh, but the triangles have318// been sorted in creating the Manifold, so the indices are converted using319// meshRelation_.faceID, which temporarily holds the mapping.320std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges(321const std::vector<Smoothness>& sharpenedEdges) const {322std::unordered_map<int, int> oldHalfedge2New;323for (size_t tri = 0; tri < NumTri(); ++tri) {324int oldTri = meshRelation_.triRef[tri].faceID;325for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;326}327std::vector<Smoothness> newSharp = sharpenedEdges;328for (Smoothness& edge : newSharp) {329edge.halfedge = oldHalfedge2New[edge.halfedge];330}331return newSharp;332}333334// Find faces containing at least 3 triangles - these will not have335// interpolated normals - all their vert normals must match their face normal.336Vec<bool> Manifold::Impl::FlatFaces() const {337const int numTri = NumTri();338Vec<bool> triIsFlatFace(numTri, false);339for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,340[this, &triIsFlatFace](const int tri) {341const TriRef& ref = meshRelation_.triRef[tri];342int faceNeighbors = 0;343ivec3 faceTris = {-1, -1, -1};344for (const int j : {0, 1, 2}) {345const int neighborTri =346halfedge_[3 * tri + j].pairedHalfedge / 3;347const TriRef& jRef = meshRelation_.triRef[neighborTri];348if (jRef.SameFace(ref)) {349++faceNeighbors;350faceTris[j] = neighborTri;351}352}353if (faceNeighbors > 1) {354triIsFlatFace[tri] = true;355for (const int j : {0, 1, 2}) {356if (faceTris[j] >= 0) {357triIsFlatFace[faceTris[j]] = true;358}359}360}361});362return triIsFlatFace;363}364365// Returns a vector of length numVert that has a tri that is part of a366// neighboring flat face if there is only one flat face. If there are none it367// gets -1, and if there are more than one it gets -2.368Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const {369Vec<int> vertFlatFace(NumVert(), -1);370Vec<TriRef> vertRef(NumVert(), {-1, -1, -1, -1});371for (size_t tri = 0; tri < NumTri(); ++tri) {372if (flatFaces[tri]) {373for (const int j : {0, 1, 2}) {374const int vert = halfedge_[3 * tri + j].startVert;375if (vertRef[vert].SameFace(meshRelation_.triRef[tri])) continue;376vertRef[vert] = meshRelation_.triRef[tri];377vertFlatFace[vert] = vertFlatFace[vert] == -1 ? tri : -2;378}379}380}381return vertFlatFace;382}383384Vec<int> Manifold::Impl::VertHalfedge() const {385Vec<int> vertHalfedge(NumVert());386Vec<uint8_t> counters(NumVert(), 0);387for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),388[&vertHalfedge, &counters, this](const int idx) {389auto old = std::atomic_exchange(390reinterpret_cast<std::atomic<uint8_t>*>(391&counters[halfedge_[idx].startVert]),392static_cast<uint8_t>(1));393if (old == 1) return;394// arbitrary, last one wins.395vertHalfedge[halfedge_[idx].startVert] = idx;396});397return vertHalfedge;398}399400std::vector<Smoothness> Manifold::Impl::SharpenEdges(401double minSharpAngle, double minSmoothness) const {402std::vector<Smoothness> sharpenedEdges;403const double minRadians = radians(minSharpAngle);404for (size_t e = 0; e < halfedge_.size(); ++e) {405if (!halfedge_[e].IsForward()) continue;406const size_t pair = halfedge_[e].pairedHalfedge;407const double dihedral =408std::acos(la::dot(faceNormal_[e / 3], faceNormal_[pair / 3]));409if (dihedral > minRadians) {410sharpenedEdges.push_back({e, minSmoothness});411sharpenedEdges.push_back({pair, minSmoothness});412}413}414return sharpenedEdges;415}416417/**418* Sharpen tangents that intersect an edge to sharpen that edge. The weight is419* unchanged, as this has a squared effect on radius of curvature, except420* in the case of zero radius, which is marked with weight = 0.421*/422void Manifold::Impl::SharpenTangent(int halfedge, double smoothness) {423halfedgeTangent_[halfedge] =424vec4(smoothness * vec3(halfedgeTangent_[halfedge]),425smoothness == 0 ? 0 : halfedgeTangent_[halfedge].w);426}427428/**429* Instead of calculating the internal shared normals like CalculateNormals430* does, this method fills in vertex properties, unshared across edges that431* are bent more than minSharpAngle.432*/433void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {434if (IsEmpty()) return;435if (normalIdx < 0) return;436437const int oldNumProp = NumProp();438439Vec<bool> triIsFlatFace = FlatFaces();440Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);441Vec<int> vertNumSharp(NumVert(), 0);442for (size_t e = 0; e < halfedge_.size(); ++e) {443if (!halfedge_[e].IsForward()) continue;444const int pair = halfedge_[e].pairedHalfedge;445const int tri1 = e / 3;446const int tri2 = pair / 3;447const double dihedral =448degrees(std::acos(la::dot(faceNormal_[tri1], faceNormal_[tri2])));449if (dihedral > minSharpAngle) {450++vertNumSharp[halfedge_[e].startVert];451++vertNumSharp[halfedge_[e].endVert];452} else {453const bool faceSplit =454triIsFlatFace[tri1] != triIsFlatFace[tri2] ||455(triIsFlatFace[tri1] && triIsFlatFace[tri2] &&456!meshRelation_.triRef[tri1].SameFace(meshRelation_.triRef[tri2]));457if (vertFlatFace[halfedge_[e].startVert] == -2 && faceSplit) {458++vertNumSharp[halfedge_[e].startVert];459}460if (vertFlatFace[halfedge_[e].endVert] == -2 && faceSplit) {461++vertNumSharp[halfedge_[e].endVert];462}463}464}465466const int numProp = std::max(oldNumProp, normalIdx + 3);467Vec<double> oldProperties(numProp * NumPropVert(), 0);468properties_.swap(oldProperties);469numProp_ = numProp;470471Vec<int> oldHalfedgeProp(halfedge_.size());472for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),473[this, &oldHalfedgeProp](int i) {474oldHalfedgeProp[i] = halfedge_[i].propVert;475halfedge_[i].propVert = -1;476});477478const int numEdge = halfedge_.size();479for (int startEdge = 0; startEdge < numEdge; ++startEdge) {480if (halfedge_[startEdge].propVert >= 0) continue;481const int vert = halfedge_[startEdge].startVert;482483if (vertNumSharp[vert] < 2) { // vertex has single normal484const vec3 normal = vertFlatFace[vert] >= 0485? faceNormal_[vertFlatFace[vert]]486: vertNormal_[vert];487int lastProp = -1;488ForVert(startEdge, [&](int current) {489const int prop = oldHalfedgeProp[current];490halfedge_[current].propVert = prop;491if (prop == lastProp) return;492lastProp = prop;493// update property vertex494auto start = oldProperties.begin() + prop * oldNumProp;495std::copy(start, start + oldNumProp,496properties_.begin() + prop * numProp);497for (const int i : {0, 1, 2})498properties_[prop * numProp + normalIdx + i] = normal[i];499});500} else { // vertex has multiple normals501const vec3 centerPos = vertPos_[vert];502// Length degree503std::vector<int> group;504// Length number of normals505std::vector<vec3> normals;506int current = startEdge;507int prevFace = current / 3;508509do { // find a sharp edge to start on510int next = NextHalfedge(halfedge_[current].pairedHalfedge);511const int face = next / 3;512513const double dihedral = degrees(514std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace])));515if (dihedral > minSharpAngle ||516triIsFlatFace[face] != triIsFlatFace[prevFace] ||517(triIsFlatFace[face] && triIsFlatFace[prevFace] &&518!meshRelation_.triRef[face].SameFace(519meshRelation_.triRef[prevFace]))) {520break;521}522current = next;523prevFace = face;524} while (current != startEdge);525526const int endEdge = current;527528struct FaceEdge {529int face;530vec3 edgeVec;531};532533// calculate pseudo-normals between each sharp edge534ForVert<FaceEdge>(535endEdge,536[this, centerPos, &vertNumSharp, &vertFlatFace](int current) {537if (IsInsideQuad(current)) {538return FaceEdge({current / 3, vec3(NAN)});539}540const int vert = halfedge_[current].endVert;541vec3 pos = vertPos_[vert];542if (vertNumSharp[vert] < 2) {543// opposite vert has fixed normal544const vec3 normal = vertFlatFace[vert] >= 0545? faceNormal_[vertFlatFace[vert]]546: vertNormal_[vert];547// Flair out the normal we're calculating to give the edge a548// more constant curvature to meet the opposite normal. Achieve549// this by pointing the tangent toward the opposite bezier550// control point instead of the vert itself.551pos += vec3(552TangentFromNormal(normal, halfedge_[current].pairedHalfedge));553}554return FaceEdge({current / 3, SafeNormalize(pos - centerPos)});555},556[this, &triIsFlatFace, &normals, &group, minSharpAngle](557int, const FaceEdge& here, FaceEdge& next) {558const double dihedral = degrees(std::acos(559la::dot(faceNormal_[here.face], faceNormal_[next.face])));560if (dihedral > minSharpAngle ||561triIsFlatFace[here.face] != triIsFlatFace[next.face] ||562(triIsFlatFace[here.face] && triIsFlatFace[next.face] &&563!meshRelation_.triRef[here.face].SameFace(564meshRelation_.triRef[next.face]))) {565normals.push_back(vec3(0.0));566}567group.push_back(normals.size() - 1);568if (std::isfinite(next.edgeVec.x)) {569normals.back() +=570SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) *571AngleBetween(here.edgeVec, next.edgeVec);572} else {573next.edgeVec = here.edgeVec;574}575});576577for (auto& normal : normals) {578normal = SafeNormalize(normal);579}580581int lastGroup = 0;582int lastProp = -1;583int newProp = -1;584int idx = 0;585ForVert(endEdge, [&](int current1) {586const int prop = oldHalfedgeProp[current1];587auto start = oldProperties.begin() + prop * oldNumProp;588589if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) {590// split property vertex, duplicating but with an updated normal591lastGroup = group[idx];592newProp = NumPropVert();593properties_.resize(properties_.size() + numProp);594std::copy(start, start + oldNumProp,595properties_.begin() + newProp * numProp);596for (const int i : {0, 1, 2}) {597properties_[newProp * numProp + normalIdx + i] =598normals[group[idx]][i];599}600} else if (prop != lastProp) {601// update property vertex602lastProp = prop;603newProp = prop;604std::copy(start, start + oldNumProp,605properties_.begin() + prop * numProp);606for (const int i : {0, 1, 2})607properties_[prop * numProp + normalIdx + i] =608normals[group[idx]][i];609}610611// point to updated property vertex612halfedge_[current1].propVert = newProp;613++idx;614});615}616}617}618619/**620* Tangents get flattened to create sharp edges by setting their weight to zero.621* This is the natural limit of reducing the weight to increase the sharpness622* smoothly. This limit gives a decent shape, but it causes the parameterization623* to be stretched and compresses it near the edges, which is good for resolving624* tight curvature, but bad for property interpolation. This function fixes the625* parameter stretch at the limit for sharp edges, since there is no curvature626* to resolve. Note this also changes the overall shape - making it more evenly627* curved.628*/629void Manifold::Impl::LinearizeFlatTangents() {630const int n = halfedgeTangent_.size();631for_each_n(autoPolicy(n, 1e4), countAt(0), n, [this](const int halfedge) {632vec4& tangent = halfedgeTangent_[halfedge];633vec4& otherTangent = halfedgeTangent_[halfedge_[halfedge].pairedHalfedge];634635const bool flat[2] = {tangent.w == 0, otherTangent.w == 0};636if (!halfedge_[halfedge].IsForward() || (!flat[0] && !flat[1])) {637return;638}639640const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -641vertPos_[halfedge_[halfedge].startVert];642643if (flat[0] && flat[1]) {644tangent = vec4(edgeVec / 3.0, 1);645otherTangent = vec4(-edgeVec / 3.0, 1);646} else if (flat[0]) {647tangent = vec4((edgeVec + vec3(otherTangent)) / 2.0, 1);648} else {649otherTangent = vec4((-edgeVec + vec3(tangent)) / 2.0, 1);650}651});652}653654/**655* Redistribute the tangents around each vertex so that the angles between them656* have the same ratios as the angles of the triangles between the corresponding657* edges. This avoids folding the output shape and gives smoother results. There658* must be at least one fixed halfedge on a vertex for that vertex to be659* operated on. If there is only one, then that halfedge is not treated as660* fixed, but the whole circle is turned to an average orientation.661*/662void Manifold::Impl::DistributeTangents(const Vec<bool>& fixedHalfedges) {663const int numHalfedge = fixedHalfedges.size();664for_each_n(665autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,666[this, &fixedHalfedges](int halfedge) {667if (!fixedHalfedges[halfedge]) return;668669if (IsMarkedInsideQuad(halfedge)) {670halfedge = NextHalfedge(halfedge_[halfedge].pairedHalfedge);671}672673vec3 normal(0.0);674Vec<double> currentAngle;675Vec<double> desiredAngle;676677const vec3 approxNormal = vertNormal_[halfedge_[halfedge].startVert];678const vec3 center = vertPos_[halfedge_[halfedge].startVert];679vec3 lastEdgeVec =680SafeNormalize(vertPos_[halfedge_[halfedge].endVert] - center);681const vec3 firstTangent =682SafeNormalize(vec3(halfedgeTangent_[halfedge]));683vec3 lastTangent = firstTangent;684int current = halfedge;685do {686current = NextHalfedge(halfedge_[current].pairedHalfedge);687if (IsMarkedInsideQuad(current)) continue;688const vec3 thisEdgeVec =689SafeNormalize(vertPos_[halfedge_[current].endVert] - center);690const vec3 thisTangent =691SafeNormalize(vec3(halfedgeTangent_[current]));692normal += la::cross(thisTangent, lastTangent);693// cumulative sum694desiredAngle.push_back(695AngleBetween(thisEdgeVec, lastEdgeVec) +696(desiredAngle.size() > 0 ? desiredAngle.back() : 0));697if (current == halfedge) {698currentAngle.push_back(kTwoPi);699} else {700currentAngle.push_back(AngleBetween(thisTangent, firstTangent));701if (la::dot(approxNormal, la::cross(thisTangent, firstTangent)) <7020) {703currentAngle.back() = kTwoPi - currentAngle.back();704}705}706lastEdgeVec = thisEdgeVec;707lastTangent = thisTangent;708} while (!fixedHalfedges[current]);709710if (currentAngle.size() == 1 || la::dot(normal, normal) == 0) return;711712const double scale = currentAngle.back() / desiredAngle.back();713double offset = 0;714if (current == halfedge) { // only one - find average offset715for (size_t i = 0; i < currentAngle.size(); ++i) {716offset += Wrap(currentAngle[i] - scale * desiredAngle[i]);717}718offset /= currentAngle.size();719}720721current = halfedge;722size_t i = 0;723do {724current = NextHalfedge(halfedge_[current].pairedHalfedge);725if (IsMarkedInsideQuad(current)) continue;726desiredAngle[i] *= scale;727const double lastAngle = i > 0 ? desiredAngle[i - 1] : 0;728// shrink obtuse angles729if (desiredAngle[i] - lastAngle > kPi) {730desiredAngle[i] = lastAngle + kPi;731} else if (i + 1 < desiredAngle.size() &&732scale * desiredAngle[i + 1] - desiredAngle[i] > kPi) {733desiredAngle[i] = scale * desiredAngle[i + 1] - kPi;734}735const double angle = currentAngle[i] - desiredAngle[i] - offset;736vec3 tangent(halfedgeTangent_[current]);737const quat q = la::rotation_quat(la::normalize(normal), angle);738halfedgeTangent_[current] =739vec4(la::qrot(q, tangent), halfedgeTangent_[current].w);740++i;741} while (!fixedHalfedges[current]);742});743}744745/**746* Calculates halfedgeTangent_, allowing the manifold to be refined and747* smoothed. The tangents form weighted cubic Beziers along each edge. This748* function creates circular arcs where possible (minimizing maximum curvature),749* constrained to the indicated property normals. Across edges that form750* discontinuities in the normals, the tangent vectors are zero-length, allowing751* the shape to form a sharp corner with minimal oscillation.752*/753void Manifold::Impl::CreateTangents(int normalIdx) {754ZoneScoped;755const int numVert = NumVert();756const int numHalfedge = halfedge_.size();757halfedgeTangent_.clear();758Vec<vec4> tangent(numHalfedge);759Vec<bool> fixedHalfedge(numHalfedge, false);760761Vec<int> vertHalfedge = VertHalfedge();762for_each_n(763autoPolicy(numVert, 1e4), vertHalfedge.begin(), numVert,764[this, &tangent, &fixedHalfedge, normalIdx](int e) {765struct FlatNormal {766bool isFlatFace;767vec3 normal;768};769770ivec2 faceEdges(-1, -1);771772ForVert<FlatNormal>(773e,774[normalIdx, this](int halfedge) {775const vec3 normal = GetNormal(halfedge, normalIdx);776const vec3 diff = faceNormal_[halfedge / 3] - normal;777return FlatNormal(778{la::dot(diff, diff) < kPrecision * kPrecision, normal});779},780[&faceEdges, &tangent, &fixedHalfedge, this](781int halfedge, const FlatNormal& here, const FlatNormal& next) {782if (IsInsideQuad(halfedge)) {783tangent[halfedge] = {0, 0, 0, -1};784return;785}786// mark special edges787const vec3 diff = next.normal - here.normal;788const bool differentNormals =789la::dot(diff, diff) > kPrecision * kPrecision;790if (differentNormals || here.isFlatFace != next.isFlatFace) {791fixedHalfedge[halfedge] = true;792if (faceEdges[0] == -1) {793faceEdges[0] = halfedge;794} else if (faceEdges[1] == -1) {795faceEdges[1] = halfedge;796} else {797faceEdges[0] = -2;798}799}800// calculate tangents801if (differentNormals) {802const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -803vertPos_[halfedge_[halfedge].startVert];804const vec3 dir = la::cross(here.normal, next.normal);805tangent[halfedge] = CircularTangent(806(la::dot(dir, edgeVec) < 0 ? -1.0 : 1.0) * dir, edgeVec);807} else {808tangent[halfedge] = TangentFromNormal(here.normal, halfedge);809}810});811812if (faceEdges[0] >= 0 && faceEdges[1] >= 0) {813const vec3 edge0 = vertPos_[halfedge_[faceEdges[0]].endVert] -814vertPos_[halfedge_[faceEdges[0]].startVert];815const vec3 edge1 = vertPos_[halfedge_[faceEdges[1]].endVert] -816vertPos_[halfedge_[faceEdges[1]].startVert];817const vec3 newTangent = la::normalize(edge0) - la::normalize(edge1);818tangent[faceEdges[0]] = CircularTangent(newTangent, edge0);819tangent[faceEdges[1]] = CircularTangent(-newTangent, edge1);820} else if (faceEdges[0] == -1 && faceEdges[0] == -1) {821fixedHalfedge[e] = true;822}823});824825halfedgeTangent_.swap(tangent);826DistributeTangents(fixedHalfedge);827}828829/**830* Calculates halfedgeTangent_, allowing the manifold to be refined and831* smoothed. The tangents form weighted cubic Beziers along each edge. This832* function creates circular arcs where possible (minimizing maximum curvature),833* constrained to the vertex normals. Where sharpenedEdges are specified, the834* tangents are shortened that intersect the sharpened edge, concentrating the835* curvature there, while the tangents of the sharp edges themselves are aligned836* for continuity.837*/838void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {839ZoneScoped;840const int numHalfedge = halfedge_.size();841halfedgeTangent_.clear();842Vec<vec4> tangent(numHalfedge);843Vec<bool> fixedHalfedge(numHalfedge, false);844845Vec<int> vertHalfedge = VertHalfedge();846Vec<bool> triIsFlatFace = FlatFaces();847Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);848Vec<vec3> vertNormal = vertNormal_;849for (size_t v = 0; v < NumVert(); ++v) {850if (vertFlatFace[v] >= 0) {851vertNormal[v] = faceNormal_[vertFlatFace[v]];852}853}854855for_each_n(autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,856[&tangent, &vertNormal, this](const int edgeIdx) {857tangent[edgeIdx] =858IsInsideQuad(edgeIdx)859? vec4(0, 0, 0, -1)860: TangentFromNormal(861vertNormal[halfedge_[edgeIdx].startVert], edgeIdx);862});863864halfedgeTangent_.swap(tangent);865866// Add sharpened edges around faces, just on the face side.867for (size_t tri = 0; tri < NumTri(); ++tri) {868if (!triIsFlatFace[tri]) continue;869for (const int j : {0, 1, 2}) {870const int tri2 = halfedge_[3 * tri + j].pairedHalfedge / 3;871if (!triIsFlatFace[tri2] ||872!meshRelation_.triRef[tri].SameFace(meshRelation_.triRef[tri2])) {873sharpenedEdges.push_back({3 * tri + j, 0});874}875}876}877878using Pair = std::pair<Smoothness, Smoothness>;879// Fill in missing pairs with default smoothness = 1.880std::map<int, Pair> edges;881for (Smoothness edge : sharpenedEdges) {882if (edge.smoothness >= 1) continue;883const bool forward = halfedge_[edge.halfedge].IsForward();884const int pair = halfedge_[edge.halfedge].pairedHalfedge;885const int idx = forward ? edge.halfedge : pair;886if (edges.find(idx) == edges.end()) {887edges[idx] = {edge, {static_cast<size_t>(pair), 1}};888if (!forward) std::swap(edges[idx].first, edges[idx].second);889} else {890Smoothness& e = forward ? edges[idx].first : edges[idx].second;891e.smoothness = std::min(edge.smoothness, e.smoothness);892}893}894895std::map<int, std::vector<Pair>> vertTangents;896for (const auto& value : edges) {897const Pair edge = value.second;898vertTangents[halfedge_[edge.first.halfedge].startVert].push_back(edge);899vertTangents[halfedge_[edge.second.halfedge].startVert].push_back(900{edge.second, edge.first});901}902903const int numVert = NumVert();904for_each_n(905autoPolicy(numVert, 1e4), countAt(0), numVert,906[this, &vertTangents, &fixedHalfedge, &vertHalfedge,907&triIsFlatFace](int v) {908auto it = vertTangents.find(v);909if (it == vertTangents.end()) {910fixedHalfedge[vertHalfedge[v]] = true;911return;912}913const std::vector<Pair>& vert = it->second;914// Sharp edges that end are smooth at their terminal vert.915if (vert.size() == 1) return;916if (vert.size() == 2) { // Make continuous edge917const int first = vert[0].first.halfedge;918const int second = vert[1].first.halfedge;919fixedHalfedge[first] = true;920fixedHalfedge[second] = true;921const vec3 newTangent = la::normalize(vec3(halfedgeTangent_[first]) -922vec3(halfedgeTangent_[second]));923924const vec3 pos = vertPos_[halfedge_[first].startVert];925halfedgeTangent_[first] = CircularTangent(926newTangent, vertPos_[halfedge_[first].endVert] - pos);927halfedgeTangent_[second] = CircularTangent(928-newTangent, vertPos_[halfedge_[second].endVert] - pos);929930double smoothness =931(vert[0].second.smoothness + vert[1].first.smoothness) / 2;932ForVert(first, [this, &smoothness, &vert, first,933second](int current) {934if (current == second) {935smoothness =936(vert[1].second.smoothness + vert[0].first.smoothness) / 2;937} else if (current != first && !IsMarkedInsideQuad(current)) {938SharpenTangent(current, smoothness);939}940});941} else { // Sharpen vertex uniformly942double smoothness = 0;943double denom = 0;944for (const Pair& pair : vert) {945smoothness += pair.first.smoothness;946smoothness += pair.second.smoothness;947denom += pair.first.smoothness == 0 ? 0 : 1;948denom += pair.second.smoothness == 0 ? 0 : 1;949}950smoothness /= denom;951952ForVert(vert[0].first.halfedge,953[this, &triIsFlatFace, smoothness](int current) {954if (!IsMarkedInsideQuad(current)) {955const int pair = halfedge_[current].pairedHalfedge;956SharpenTangent(current, triIsFlatFace[current / 3] ||957triIsFlatFace[pair / 3]958? 0959: smoothness);960}961});962}963});964965LinearizeFlatTangents();966DistributeTangents(fixedHalfedge);967}968969void Manifold::Impl::Refine(std::function<int(vec3, vec4, vec4)> edgeDivisions,970bool keepInterior) {971if (IsEmpty()) return;972Manifold::Impl old = *this;973Vec<Barycentric> vertBary = Subdivide(edgeDivisions, keepInterior);974if (vertBary.size() == 0) return;975976if (old.halfedgeTangent_.size() == old.halfedge_.size()) {977for_each_n(autoPolicy(NumTri(), 1e4), countAt(0), NumVert(),978InterpTri({vertPos_, vertBary, &old}));979}980981halfedgeTangent_.clear();982Finish();983if (old.halfedgeTangent_.size() == old.halfedge_.size()) {984MarkCoplanar();985}986meshRelation_.originalID = -1;987}988989} // namespace manifold990991992