Path: blob/master/thirdparty/manifold/src/smoothing.cpp
20849 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 <unordered_map>1516#include "impl.h"17#include "parallel.h"1819namespace {20using namespace manifold;2122// Returns a normalized vector orthogonal to ref, in the plane of ref and in,23// unless in and ref are colinear, in which case it falls back to the plane of24// ref and altIn.25vec3 OrthogonalTo(vec3 in, vec3 altIn, vec3 ref) {26vec3 out = in - la::dot(in, ref) * ref;27if (la::dot(out, out) < kPrecision * la::dot(in, in)) {28out = altIn - la::dot(altIn, ref) * ref;29}30return SafeNormalize(out);31}3233double Wrap(double radians) {34return radians < -kPi ? radians + kTwoPi35: radians > kPi ? radians - kTwoPi36: radians;37}3839// Get the angle between two unit-vectors.40double AngleBetween(vec3 a, vec3 b) {41const double dot = la::dot(a, b);42return dot >= 1 ? 0 : (dot <= -1 ? kPi : la::acos(dot));43}4445// Calculate a tangent vector in the form of a weighted cubic Bezier taking as46// input the desired tangent direction (length doesn't matter) and the edge47// vector to the neighboring vertex. In a symmetric situation where the tangents48// at each end are mirror images of each other, this will result in a circular49// arc.50vec4 CircularTangent(const vec3& tangent, const vec3& edgeVec) {51const vec3 dir = SafeNormalize(tangent);5253double weight = std::max(0.5, la::dot(dir, SafeNormalize(edgeVec)));54// Quadratic weighted bezier for circular interpolation55const vec4 bz2 = vec4(dir * 0.5 * la::length(edgeVec), weight);56// Equivalent cubic weighted bezier57const vec4 bz3 = la::lerp(vec4(0, 0, 0, 1), bz2, 2 / 3.0);58// Convert from homogeneous form to geometric form59return vec4(vec3(bz3) / bz3.w, bz3.w);60}6162struct InterpTri {63VecView<vec3> vertPos;64VecView<const Barycentric> vertBary;65const Manifold::Impl* impl;6667static vec4 Homogeneous(vec4 v) {68v.x *= v.w;69v.y *= v.w;70v.z *= v.w;71return v;72}7374static vec4 Homogeneous(vec3 v) { return vec4(v, 1.0); }7576static vec3 HNormalize(vec4 v) {77return v.w == 0 ? vec3(v) : (vec3(v) / v.w);78}7980static vec4 Scale(vec4 v, double scale) { return vec4(scale * vec3(v), v.w); }8182static vec4 Bezier(vec3 point, vec4 tangent) {83return Homogeneous(vec4(point, 0) + tangent);84}8586static mat4x2 CubicBezier2Linear(vec4 p0, vec4 p1, vec4 p2, vec4 p3,87double x) {88mat4x2 out;89vec4 p12 = la::lerp(p1, p2, x);90out[0] = la::lerp(la::lerp(p0, p1, x), p12, x);91out[1] = la::lerp(p12, la::lerp(p2, p3, x), x);92return out;93}9495static vec3 BezierPoint(mat4x2 points, double x) {96return HNormalize(la::lerp(points[0], points[1], x));97}9899static vec3 BezierTangent(mat4x2 points) {100return SafeNormalize(HNormalize(points[1]) - HNormalize(points[0]));101}102103static vec3 RotateFromTo(vec3 v, quat start, quat end) {104return la::qrot(end, la::qrot(la::qconj(start), v));105}106107static quat Slerp(const quat& x, const quat& y, double a, bool longWay) {108quat z = y;109double cosTheta = la::dot(x, y);110111// Take the long way around the sphere only when requested112if ((cosTheta < 0) != longWay) {113z = -y;114cosTheta = -cosTheta;115}116117if (cosTheta > 1.0 - std::numeric_limits<double>::epsilon()) {118return la::lerp(x, z, a); // for numerical stability119} else {120double angle = std::acos(cosTheta);121return (std::sin((1.0 - a) * angle) * x + std::sin(a * angle) * z) /122std::sin(angle);123}124}125126static mat4x2 Bezier2Bezier(const mat3x2& corners, const mat4x2& tangentsX,127const mat4x2& tangentsY, double x,128const vec3& anchor) {129const mat4x2 bez = CubicBezier2Linear(130Homogeneous(corners[0]), Bezier(corners[0], tangentsX[0]),131Bezier(corners[1], tangentsX[1]), Homogeneous(corners[1]), x);132const vec3 end = BezierPoint(bez, x);133const vec3 tangent = BezierTangent(bez);134135const mat3x2 nTangentsX(SafeNormalize(vec3(tangentsX[0])),136-SafeNormalize(vec3(tangentsX[1])));137const mat3x2 biTangents = {138OrthogonalTo(vec3(tangentsY[0]), (anchor - corners[0]), nTangentsX[0]),139OrthogonalTo(vec3(tangentsY[1]), (anchor - corners[1]), nTangentsX[1])};140141const quat q0 = la::rotation_quat(mat3(142nTangentsX[0], biTangents[0], la::cross(nTangentsX[0], biTangents[0])));143const quat q1 = la::rotation_quat(mat3(144nTangentsX[1], biTangents[1], la::cross(nTangentsX[1], biTangents[1])));145const vec3 edge = corners[1] - corners[0];146const bool longWay =147la::dot(nTangentsX[0], edge) + la::dot(nTangentsX[1], edge) < 0;148const quat qTmp = Slerp(q0, q1, x, longWay);149const quat q = la::qmul(la::rotation_quat(la::qxdir(qTmp), tangent), qTmp);150151const vec3 delta = la::lerp(RotateFromTo(vec3(tangentsY[0]), q0, q),152RotateFromTo(vec3(tangentsY[1]), q1, q), x);153const double deltaW = la::lerp(tangentsY[0].w, tangentsY[1].w, x);154155return {Homogeneous(end), vec4(delta, deltaW)};156}157158static vec3 Bezier2D(const mat3x4& corners, const mat4& tangentsX,159const mat4& tangentsY, double x, double y,160const vec3& centroid) {161mat4x2 bez0 =162Bezier2Bezier({corners[0], corners[1]}, {tangentsX[0], tangentsX[1]},163{tangentsY[0], tangentsY[1]}, x, centroid);164mat4x2 bez1 =165Bezier2Bezier({corners[2], corners[3]}, {tangentsX[2], tangentsX[3]},166{tangentsY[2], tangentsY[3]}, 1 - x, centroid);167168const mat4x2 bez =169CubicBezier2Linear(bez0[0], Bezier(vec3(bez0[0]), bez0[1]),170Bezier(vec3(bez1[0]), bez1[1]), bez1[0], y);171return BezierPoint(bez, y);172}173174void operator()(const int vert) {175vec3& pos = vertPos[vert];176const int tri = vertBary[vert].tri;177const vec4 uvw = vertBary[vert].uvw;178179const ivec4 halfedges = impl->GetHalfedges(tri);180const mat3x4 corners = {181impl->vertPos_[impl->halfedge_[halfedges[0]].startVert],182impl->vertPos_[impl->halfedge_[halfedges[1]].startVert],183impl->vertPos_[impl->halfedge_[halfedges[2]].startVert],184halfedges[3] < 0185? vec3(0.0)186: impl->vertPos_[impl->halfedge_[halfedges[3]].startVert]};187188for (const int i : {0, 1, 2, 3}) {189if (uvw[i] == 1) {190pos = corners[i];191return;192}193}194195vec4 posH(0.0);196197if (halfedges[3] < 0) { // tri198const mat4x3 tangentR = {impl->halfedgeTangent_[halfedges[0]],199impl->halfedgeTangent_[halfedges[1]],200impl->halfedgeTangent_[halfedges[2]]};201const mat4x3 tangentL = {202impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge],203impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],204impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge]};205const vec3 centroid = mat3(corners) * vec3(1.0 / 3);206207for (const int i : {0, 1, 2}) {208const int j = Next3(i);209const int k = Prev3(i);210const double x = uvw[k] / (1 - uvw[i]);211212const mat4x2 bez =213Bezier2Bezier({corners[j], corners[k]}, {tangentR[j], tangentL[k]},214{tangentL[j], tangentR[k]}, x, centroid);215216const mat4x2 bez1 = CubicBezier2Linear(217bez[0], Bezier(vec3(bez[0]), bez[1]),218Bezier(corners[i], la::lerp(tangentR[i], tangentL[i], x)),219Homogeneous(corners[i]), uvw[i]);220const vec3 p = BezierPoint(bez1, uvw[i]);221posH += Homogeneous(vec4(p, uvw[j] * uvw[k]));222}223} else { // quad224const mat4 tangentsX = {225impl->halfedgeTangent_[halfedges[0]],226impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],227impl->halfedgeTangent_[halfedges[2]],228impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge]};229const mat4 tangentsY = {230impl->halfedgeTangent_[impl->halfedge_[halfedges[3]].pairedHalfedge],231impl->halfedgeTangent_[halfedges[1]],232impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge],233impl->halfedgeTangent_[halfedges[3]]};234const vec3 centroid = corners * vec4(0.25);235const double x = uvw[1] + uvw[2];236const double y = uvw[2] + uvw[3];237const vec3 pX = Bezier2D(corners, tangentsX, tangentsY, x, y, centroid);238const vec3 pY =239Bezier2D({corners[1], corners[2], corners[3], corners[0]},240{tangentsY[1], tangentsY[2], tangentsY[3], tangentsY[0]},241{tangentsX[1], tangentsX[2], tangentsX[3], tangentsX[0]}, y,2421 - x, centroid);243posH += Homogeneous(vec4(pX, x * (1 - x)));244posH += Homogeneous(vec4(pY, y * (1 - y)));245}246pos = HNormalize(posH);247}248};249} // namespace250251namespace manifold {252253/**254* Get the property normal associated with the startVert of this halfedge, where255* normalIdx shows the beginning of where normals are stored in the properties.256*/257vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const {258const int prop = halfedge_[halfedge].propVert;259vec3 normal;260for (const int i : {0, 1, 2}) {261normal[i] = properties_[prop * numProp_ + normalIdx + i];262}263return normal;264}265266/**267* Returns a circular tangent for the requested halfedge, orthogonal to the268* given normal vector, and avoiding folding.269*/270vec4 Manifold::Impl::TangentFromNormal(const vec3& normal, int halfedge) const {271const Halfedge edge = halfedge_[halfedge];272const vec3 edgeVec = vertPos_[edge.endVert] - vertPos_[edge.startVert];273const vec3 edgeNormal =274faceNormal_[halfedge / 3] + faceNormal_[edge.pairedHalfedge / 3];275vec3 dir = la::cross(la::cross(edgeNormal, edgeVec), normal);276return CircularTangent(dir, edgeVec);277}278279/**280* Returns true if this halfedge should be marked as the interior of a quad, as281* defined by its two triangles referring to the same face, and those triangles282* having no further face neighbors beyond.283*/284bool Manifold::Impl::IsInsideQuad(int halfedge) const {285if (halfedgeTangent_.size() > 0) {286return halfedgeTangent_[halfedge].w < 0;287}288const int tri = halfedge / 3;289const TriRef ref = meshRelation_.triRef[tri];290const int pair = halfedge_[halfedge].pairedHalfedge;291const int pairTri = pair / 3;292const TriRef pairRef = meshRelation_.triRef[pairTri];293if (!ref.SameFace(pairRef)) return false;294295auto SameFace = [this](int halfedge, const TriRef& ref) {296return ref.SameFace(297meshRelation_.triRef[halfedge_[halfedge].pairedHalfedge / 3]);298};299300int neighbor = NextHalfedge(halfedge);301if (SameFace(neighbor, ref)) return false;302neighbor = NextHalfedge(neighbor);303if (SameFace(neighbor, ref)) return false;304neighbor = NextHalfedge(pair);305if (SameFace(neighbor, pairRef)) return false;306neighbor = NextHalfedge(neighbor);307if (SameFace(neighbor, pairRef)) return false;308return true;309}310311/**312* Returns true if this halfedge is an interior of a quad, as defined by its313* halfedge tangent having negative weight.314*/315bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const {316return halfedgeTangent_.size() > 0 && halfedgeTangent_[halfedge].w < 0;317}318319// sharpenedEdges are referenced to the input Mesh, but the triangles have320// been sorted in creating the Manifold, so the indices are converted using321// meshRelation_.faceID, which temporarily holds the mapping.322std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges(323const std::vector<Smoothness>& sharpenedEdges) const {324std::unordered_map<int, int> oldHalfedge2New;325for (size_t tri = 0; tri < NumTri(); ++tri) {326int oldTri = meshRelation_.triRef[tri].faceID;327for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;328}329std::vector<Smoothness> newSharp = sharpenedEdges;330for (Smoothness& edge : newSharp) {331edge.halfedge = oldHalfedge2New[edge.halfedge];332}333return newSharp;334}335336// Find faces containing at least 3 triangles - these will not have337// interpolated normals - all their vert normals must match their face normal.338Vec<bool> Manifold::Impl::FlatFaces() const {339const int numTri = NumTri();340Vec<bool> triIsFlatFace(numTri, false);341for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,342[this, &triIsFlatFace](const int tri) {343const TriRef& ref = meshRelation_.triRef[tri];344int faceNeighbors = 0;345ivec3 faceTris = {-1, -1, -1};346for (const int j : {0, 1, 2}) {347const int neighborTri =348halfedge_[3 * tri + j].pairedHalfedge / 3;349const TriRef& jRef = meshRelation_.triRef[neighborTri];350if (jRef.SameFace(ref)) {351++faceNeighbors;352faceTris[j] = neighborTri;353}354}355if (faceNeighbors > 1) {356triIsFlatFace[tri] = true;357for (const int j : {0, 1, 2}) {358if (faceTris[j] >= 0) {359triIsFlatFace[faceTris[j]] = true;360}361}362}363});364return triIsFlatFace;365}366367// Returns a vector of length numVert that has a tri that is part of a368// neighboring flat face if there is only one flat face. If there are none it369// gets -1, and if there are more than one it gets -2.370Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const {371Vec<int> vertFlatFace(NumVert(), -1);372Vec<TriRef> vertRef(NumVert(), {-1, -1, -1, -1});373for (size_t tri = 0; tri < NumTri(); ++tri) {374if (flatFaces[tri]) {375for (const int j : {0, 1, 2}) {376const int vert = halfedge_[3 * tri + j].startVert;377if (vertRef[vert].SameFace(meshRelation_.triRef[tri])) continue;378vertRef[vert] = meshRelation_.triRef[tri];379vertFlatFace[vert] = vertFlatFace[vert] == -1 ? tri : -2;380}381}382}383return vertFlatFace;384}385386Vec<int> Manifold::Impl::VertHalfedge() const {387Vec<int> vertHalfedge(NumVert());388Vec<uint8_t> counters(NumVert(), 0);389for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),390[&vertHalfedge, &counters, this](const int idx) {391auto old = std::atomic_exchange(392reinterpret_cast<std::atomic<uint8_t>*>(393&counters[halfedge_[idx].startVert]),394static_cast<uint8_t>(1));395if (old == 1) return;396// arbitrary, last one wins.397vertHalfedge[halfedge_[idx].startVert] = idx;398});399return vertHalfedge;400}401402std::vector<Smoothness> Manifold::Impl::SharpenEdges(403double minSharpAngle, double minSmoothness) const {404std::vector<Smoothness> sharpenedEdges;405const double minRadians = radians(minSharpAngle);406for (size_t e = 0; e < halfedge_.size(); ++e) {407if (!halfedge_[e].IsForward()) continue;408const size_t pair = halfedge_[e].pairedHalfedge;409const double dihedral =410std::acos(la::dot(faceNormal_[e / 3], faceNormal_[pair / 3]));411if (dihedral > minRadians) {412sharpenedEdges.push_back({e, minSmoothness});413sharpenedEdges.push_back({pair, minSmoothness});414}415}416return sharpenedEdges;417}418419/**420* Sharpen tangents that intersect an edge to sharpen that edge. The weight is421* unchanged, as this has a squared effect on radius of curvature, except422* in the case of zero radius, which is marked with weight = 0.423*/424void Manifold::Impl::SharpenTangent(int halfedge, double smoothness) {425halfedgeTangent_[halfedge] =426vec4(smoothness * vec3(halfedgeTangent_[halfedge]),427smoothness == 0 ? 0 : halfedgeTangent_[halfedge].w);428}429430/**431* Instead of calculating the internal shared normals like CalculateNormals432* does, this method fills in vertex properties, unshared across edges that433* are bent more than minSharpAngle.434*/435void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {436if (IsEmpty()) return;437if (normalIdx < 0) return;438439const int oldNumProp = NumProp();440441Vec<bool> triIsFlatFace = FlatFaces();442Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);443Vec<int> vertNumSharp(NumVert(), 0);444for (size_t e = 0; e < halfedge_.size(); ++e) {445if (!halfedge_[e].IsForward()) continue;446const int pair = halfedge_[e].pairedHalfedge;447const int tri1 = e / 3;448const int tri2 = pair / 3;449const double dihedral =450degrees(std::acos(la::dot(faceNormal_[tri1], faceNormal_[tri2])));451if (dihedral > minSharpAngle) {452++vertNumSharp[halfedge_[e].startVert];453++vertNumSharp[halfedge_[e].endVert];454} else {455const bool faceSplit =456triIsFlatFace[tri1] != triIsFlatFace[tri2] ||457(triIsFlatFace[tri1] && triIsFlatFace[tri2] &&458!meshRelation_.triRef[tri1].SameFace(meshRelation_.triRef[tri2]));459if (vertFlatFace[halfedge_[e].startVert] == -2 && faceSplit) {460++vertNumSharp[halfedge_[e].startVert];461}462if (vertFlatFace[halfedge_[e].endVert] == -2 && faceSplit) {463++vertNumSharp[halfedge_[e].endVert];464}465}466}467468const int numProp = std::max(oldNumProp, normalIdx + 3);469Vec<double> oldProperties(numProp * NumPropVert(), 0);470properties_.swap(oldProperties);471numProp_ = numProp;472473Vec<int> oldHalfedgeProp(halfedge_.size());474for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),475[this, &oldHalfedgeProp](int i) {476oldHalfedgeProp[i] = halfedge_[i].propVert;477halfedge_[i].propVert = -1;478});479480const int numEdge = halfedge_.size();481for (int startEdge = 0; startEdge < numEdge; ++startEdge) {482if (halfedge_[startEdge].propVert >= 0) continue;483const int vert = halfedge_[startEdge].startVert;484485if (vertNumSharp[vert] < 2) { // vertex has single normal486const vec3 normal = vertFlatFace[vert] >= 0487? faceNormal_[vertFlatFace[vert]]488: vertNormal_[vert];489int lastProp = -1;490ForVert(startEdge, [&](int current) {491const int prop = oldHalfedgeProp[current];492halfedge_[current].propVert = prop;493if (prop == lastProp) return;494lastProp = prop;495// update property vertex496auto start = oldProperties.begin() + prop * oldNumProp;497std::copy(start, start + oldNumProp,498properties_.begin() + prop * numProp);499for (const int i : {0, 1, 2})500properties_[prop * numProp + normalIdx + i] = normal[i];501});502} else { // vertex has multiple normals503const vec3 centerPos = vertPos_[vert];504// Length degree505std::vector<int> group;506// Length number of normals507std::vector<vec3> normals;508int current = startEdge;509int prevFace = current / 3;510511do { // find a sharp edge to start on512int next = NextHalfedge(halfedge_[current].pairedHalfedge);513const int face = next / 3;514515const double dihedral = degrees(516std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace])));517if (dihedral > minSharpAngle ||518triIsFlatFace[face] != triIsFlatFace[prevFace] ||519(triIsFlatFace[face] && triIsFlatFace[prevFace] &&520!meshRelation_.triRef[face].SameFace(521meshRelation_.triRef[prevFace]))) {522break;523}524current = next;525prevFace = face;526} while (current != startEdge);527528const int endEdge = current;529530struct FaceEdge {531int face;532vec3 edgeVec;533};534535// calculate pseudo-normals between each sharp edge536ForVert<FaceEdge>(537endEdge,538[this, centerPos, &vertNumSharp, &vertFlatFace](int current) {539if (IsInsideQuad(current)) {540return FaceEdge({current / 3, vec3(NAN)});541}542const int vert = halfedge_[current].endVert;543vec3 pos = vertPos_[vert];544if (vertNumSharp[vert] < 2) {545// opposite vert has fixed normal546const vec3 normal = vertFlatFace[vert] >= 0547? faceNormal_[vertFlatFace[vert]]548: vertNormal_[vert];549// Flair out the normal we're calculating to give the edge a550// more constant curvature to meet the opposite normal. Achieve551// this by pointing the tangent toward the opposite bezier552// control point instead of the vert itself.553pos += vec3(554TangentFromNormal(normal, halfedge_[current].pairedHalfedge));555}556return FaceEdge({current / 3, SafeNormalize(pos - centerPos)});557},558[this, &triIsFlatFace, &normals, &group, minSharpAngle](559int, const FaceEdge& here, FaceEdge& next) {560const double dihedral = degrees(std::acos(561la::dot(faceNormal_[here.face], faceNormal_[next.face])));562if (dihedral > minSharpAngle ||563triIsFlatFace[here.face] != triIsFlatFace[next.face] ||564(triIsFlatFace[here.face] && triIsFlatFace[next.face] &&565!meshRelation_.triRef[here.face].SameFace(566meshRelation_.triRef[next.face]))) {567normals.push_back(vec3(0.0));568}569group.push_back(normals.size() - 1);570if (std::isfinite(next.edgeVec.x)) {571normals.back() +=572SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) *573AngleBetween(here.edgeVec, next.edgeVec);574} else {575next.edgeVec = here.edgeVec;576}577});578579for (auto& normal : normals) {580normal = SafeNormalize(normal);581}582583int lastGroup = 0;584int lastProp = -1;585int newProp = -1;586int idx = 0;587ForVert(endEdge, [&](int current1) {588const int prop = oldHalfedgeProp[current1];589auto start = oldProperties.begin() + prop * oldNumProp;590591if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) {592// split property vertex, duplicating but with an updated normal593lastGroup = group[idx];594newProp = NumPropVert();595properties_.resize(properties_.size() + numProp);596std::copy(start, start + oldNumProp,597properties_.begin() + newProp * numProp);598for (const int i : {0, 1, 2}) {599properties_[newProp * numProp + normalIdx + i] =600normals[group[idx]][i];601}602} else if (prop != lastProp) {603// update property vertex604lastProp = prop;605newProp = prop;606std::copy(start, start + oldNumProp,607properties_.begin() + prop * numProp);608for (const int i : {0, 1, 2})609properties_[prop * numProp + normalIdx + i] =610normals[group[idx]][i];611}612613// point to updated property vertex614halfedge_[current1].propVert = newProp;615++idx;616});617}618}619}620621/**622* Tangents get flattened to create sharp edges by setting their weight to zero.623* This is the natural limit of reducing the weight to increase the sharpness624* smoothly. This limit gives a decent shape, but it causes the parameterization625* to be stretched and compresses it near the edges, which is good for resolving626* tight curvature, but bad for property interpolation. This function fixes the627* parameter stretch at the limit for sharp edges, since there is no curvature628* to resolve. Note this also changes the overall shape - making it more evenly629* curved.630*/631void Manifold::Impl::LinearizeFlatTangents() {632const int n = halfedgeTangent_.size();633for_each_n(autoPolicy(n, 1e4), countAt(0), n, [this](const int halfedge) {634vec4& tangent = halfedgeTangent_[halfedge];635vec4& otherTangent = halfedgeTangent_[halfedge_[halfedge].pairedHalfedge];636637const bool flat[2] = {tangent.w == 0, otherTangent.w == 0};638if (!halfedge_[halfedge].IsForward() || (!flat[0] && !flat[1])) {639return;640}641642const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -643vertPos_[halfedge_[halfedge].startVert];644645if (flat[0] && flat[1]) {646tangent = vec4(edgeVec / 3.0, 1);647otherTangent = vec4(-edgeVec / 3.0, 1);648} else if (flat[0]) {649tangent = vec4((edgeVec + vec3(otherTangent)) / 2.0, 1);650} else {651otherTangent = vec4((-edgeVec + vec3(tangent)) / 2.0, 1);652}653});654}655656/**657* Redistribute the tangents around each vertex so that the angles between them658* have the same ratios as the angles of the triangles between the corresponding659* edges. This avoids folding the output shape and gives smoother results. There660* must be at least one fixed halfedge on a vertex for that vertex to be661* operated on. If there is only one, then that halfedge is not treated as662* fixed, but the whole circle is turned to an average orientation.663*/664void Manifold::Impl::DistributeTangents(const Vec<bool>& fixedHalfedges) {665const int numHalfedge = fixedHalfedges.size();666for_each_n(667autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,668[this, &fixedHalfedges](int halfedge) {669if (!fixedHalfedges[halfedge]) return;670671if (IsMarkedInsideQuad(halfedge)) {672halfedge = NextHalfedge(halfedge_[halfedge].pairedHalfedge);673}674675vec3 normal(0.0);676Vec<double> currentAngle;677Vec<double> desiredAngle;678679const vec3 approxNormal = vertNormal_[halfedge_[halfedge].startVert];680const vec3 center = vertPos_[halfedge_[halfedge].startVert];681vec3 lastEdgeVec =682SafeNormalize(vertPos_[halfedge_[halfedge].endVert] - center);683const vec3 firstTangent =684SafeNormalize(vec3(halfedgeTangent_[halfedge]));685vec3 lastTangent = firstTangent;686int current = halfedge;687do {688current = NextHalfedge(halfedge_[current].pairedHalfedge);689if (IsMarkedInsideQuad(current)) continue;690const vec3 thisEdgeVec =691SafeNormalize(vertPos_[halfedge_[current].endVert] - center);692const vec3 thisTangent =693SafeNormalize(vec3(halfedgeTangent_[current]));694normal += la::cross(thisTangent, lastTangent);695// cumulative sum696desiredAngle.push_back(697AngleBetween(thisEdgeVec, lastEdgeVec) +698(desiredAngle.size() > 0 ? desiredAngle.back() : 0));699if (current == halfedge) {700currentAngle.push_back(kTwoPi);701} else {702currentAngle.push_back(AngleBetween(thisTangent, firstTangent));703if (la::dot(approxNormal, la::cross(thisTangent, firstTangent)) <7040) {705currentAngle.back() = kTwoPi - currentAngle.back();706}707}708lastEdgeVec = thisEdgeVec;709lastTangent = thisTangent;710} while (!fixedHalfedges[current]);711712if (currentAngle.size() == 1 || la::dot(normal, normal) == 0) return;713714const double scale = currentAngle.back() / desiredAngle.back();715double offset = 0;716if (current == halfedge) { // only one - find average offset717for (size_t i = 0; i < currentAngle.size(); ++i) {718offset += Wrap(currentAngle[i] - scale * desiredAngle[i]);719}720offset /= currentAngle.size();721}722723current = halfedge;724size_t i = 0;725do {726current = NextHalfedge(halfedge_[current].pairedHalfedge);727if (IsMarkedInsideQuad(current)) continue;728desiredAngle[i] *= scale;729const double lastAngle = i > 0 ? desiredAngle[i - 1] : 0;730// shrink obtuse angles731if (desiredAngle[i] - lastAngle > kPi) {732desiredAngle[i] = lastAngle + kPi;733} else if (i + 1 < desiredAngle.size() &&734scale * desiredAngle[i + 1] - desiredAngle[i] > kPi) {735desiredAngle[i] = scale * desiredAngle[i + 1] - kPi;736}737const double angle = currentAngle[i] - desiredAngle[i] - offset;738vec3 tangent(halfedgeTangent_[current]);739const quat q = la::rotation_quat(la::normalize(normal), angle);740halfedgeTangent_[current] =741vec4(la::qrot(q, tangent), halfedgeTangent_[current].w);742++i;743} while (!fixedHalfedges[current]);744});745}746747/**748* Calculates halfedgeTangent_, allowing the manifold to be refined and749* smoothed. The tangents form weighted cubic Beziers along each edge. This750* function creates circular arcs where possible (minimizing maximum curvature),751* constrained to the indicated property normals. Across edges that form752* discontinuities in the normals, the tangent vectors are zero-length, allowing753* the shape to form a sharp corner with minimal oscillation.754*/755void Manifold::Impl::CreateTangents(int normalIdx) {756ZoneScoped;757const int numVert = NumVert();758const int numHalfedge = halfedge_.size();759halfedgeTangent_.clear();760Vec<vec4> tangent(numHalfedge);761Vec<bool> fixedHalfedge(numHalfedge, false);762763Vec<int> vertHalfedge = VertHalfedge();764for_each_n(765autoPolicy(numVert, 1e4), vertHalfedge.begin(), numVert,766[this, &tangent, &fixedHalfedge, normalIdx](int e) {767struct FlatNormal {768bool isFlatFace;769vec3 normal;770};771772ivec2 faceEdges(-1, -1);773774ForVert<FlatNormal>(775e,776[normalIdx, this](int halfedge) {777const vec3 normal = GetNormal(halfedge, normalIdx);778const vec3 diff = faceNormal_[halfedge / 3] - normal;779return FlatNormal(780{la::dot(diff, diff) < kPrecision * kPrecision, normal});781},782[&faceEdges, &tangent, &fixedHalfedge, this](783int halfedge, const FlatNormal& here, const FlatNormal& next) {784if (IsInsideQuad(halfedge)) {785tangent[halfedge] = {0, 0, 0, -1};786return;787}788// mark special edges789const vec3 diff = next.normal - here.normal;790const bool differentNormals =791la::dot(diff, diff) > kPrecision * kPrecision;792if (differentNormals || here.isFlatFace != next.isFlatFace) {793fixedHalfedge[halfedge] = true;794if (faceEdges[0] == -1) {795faceEdges[0] = halfedge;796} else if (faceEdges[1] == -1) {797faceEdges[1] = halfedge;798} else {799faceEdges[0] = -2;800}801}802// calculate tangents803if (differentNormals) {804const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -805vertPos_[halfedge_[halfedge].startVert];806const vec3 dir = la::cross(here.normal, next.normal);807tangent[halfedge] = CircularTangent(808(la::dot(dir, edgeVec) < 0 ? -1.0 : 1.0) * dir, edgeVec);809} else {810tangent[halfedge] = TangentFromNormal(here.normal, halfedge);811}812});813814if (faceEdges[0] >= 0 && faceEdges[1] >= 0) {815const vec3 edge0 = vertPos_[halfedge_[faceEdges[0]].endVert] -816vertPos_[halfedge_[faceEdges[0]].startVert];817const vec3 edge1 = vertPos_[halfedge_[faceEdges[1]].endVert] -818vertPos_[halfedge_[faceEdges[1]].startVert];819const vec3 newTangent = la::normalize(edge0) - la::normalize(edge1);820tangent[faceEdges[0]] = CircularTangent(newTangent, edge0);821tangent[faceEdges[1]] = CircularTangent(-newTangent, edge1);822} else if (faceEdges[0] == -1 && faceEdges[0] == -1) {823fixedHalfedge[e] = true;824}825});826827halfedgeTangent_.swap(tangent);828DistributeTangents(fixedHalfedge);829}830831/**832* Calculates halfedgeTangent_, allowing the manifold to be refined and833* smoothed. The tangents form weighted cubic Beziers along each edge. This834* function creates circular arcs where possible (minimizing maximum curvature),835* constrained to the vertex normals. Where sharpenedEdges are specified, the836* tangents are shortened that intersect the sharpened edge, concentrating the837* curvature there, while the tangents of the sharp edges themselves are aligned838* for continuity.839*/840void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {841ZoneScoped;842const int numHalfedge = halfedge_.size();843halfedgeTangent_.clear();844Vec<vec4> tangent(numHalfedge);845Vec<bool> fixedHalfedge(numHalfedge, false);846847Vec<int> vertHalfedge = VertHalfedge();848Vec<bool> triIsFlatFace = FlatFaces();849Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);850Vec<vec3> vertNormal = vertNormal_;851for (size_t v = 0; v < NumVert(); ++v) {852if (vertFlatFace[v] >= 0) {853vertNormal[v] = faceNormal_[vertFlatFace[v]];854}855}856857for_each_n(autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,858[&tangent, &vertNormal, this](const int edgeIdx) {859tangent[edgeIdx] =860IsInsideQuad(edgeIdx)861? vec4(0, 0, 0, -1)862: TangentFromNormal(863vertNormal[halfedge_[edgeIdx].startVert], edgeIdx);864});865866halfedgeTangent_.swap(tangent);867868// Add sharpened edges around faces, just on the face side.869for (size_t tri = 0; tri < NumTri(); ++tri) {870if (!triIsFlatFace[tri]) continue;871for (const int j : {0, 1, 2}) {872const int tri2 = halfedge_[3 * tri + j].pairedHalfedge / 3;873if (!triIsFlatFace[tri2] ||874!meshRelation_.triRef[tri].SameFace(meshRelation_.triRef[tri2])) {875sharpenedEdges.push_back({3 * tri + j, 0});876}877}878}879880using Pair = std::pair<Smoothness, Smoothness>;881// Fill in missing pairs with default smoothness = 1.882std::map<int, Pair> edges;883for (Smoothness edge : sharpenedEdges) {884if (edge.smoothness >= 1) continue;885const bool forward = halfedge_[edge.halfedge].IsForward();886const int pair = halfedge_[edge.halfedge].pairedHalfedge;887const int idx = forward ? edge.halfedge : pair;888if (edges.find(idx) == edges.end()) {889edges[idx] = {edge, {static_cast<size_t>(pair), 1}};890if (!forward) std::swap(edges[idx].first, edges[idx].second);891} else {892Smoothness& e = forward ? edges[idx].first : edges[idx].second;893e.smoothness = std::min(edge.smoothness, e.smoothness);894}895}896897std::map<int, std::vector<Pair>> vertTangents;898for (const auto& value : edges) {899const Pair edge = value.second;900vertTangents[halfedge_[edge.first.halfedge].startVert].push_back(edge);901vertTangents[halfedge_[edge.second.halfedge].startVert].push_back(902{edge.second, edge.first});903}904905const int numVert = NumVert();906for_each_n(907autoPolicy(numVert, 1e4), countAt(0), numVert,908[this, &vertTangents, &fixedHalfedge, &vertHalfedge,909&triIsFlatFace](int v) {910auto it = vertTangents.find(v);911if (it == vertTangents.end()) {912fixedHalfedge[vertHalfedge[v]] = true;913return;914}915const std::vector<Pair>& vert = it->second;916// Sharp edges that end are smooth at their terminal vert.917if (vert.size() == 1) return;918if (vert.size() == 2) { // Make continuous edge919const int first = vert[0].first.halfedge;920const int second = vert[1].first.halfedge;921fixedHalfedge[first] = true;922fixedHalfedge[second] = true;923const vec3 newTangent = la::normalize(vec3(halfedgeTangent_[first]) -924vec3(halfedgeTangent_[second]));925926const vec3 pos = vertPos_[halfedge_[first].startVert];927halfedgeTangent_[first] = CircularTangent(928newTangent, vertPos_[halfedge_[first].endVert] - pos);929halfedgeTangent_[second] = CircularTangent(930-newTangent, vertPos_[halfedge_[second].endVert] - pos);931932double smoothness =933(vert[0].second.smoothness + vert[1].first.smoothness) / 2;934ForVert(first, [this, &smoothness, &vert, first,935second](int current) {936if (current == second) {937smoothness =938(vert[1].second.smoothness + vert[0].first.smoothness) / 2;939} else if (current != first && !IsMarkedInsideQuad(current)) {940SharpenTangent(current, smoothness);941}942});943} else { // Sharpen vertex uniformly944double smoothness = 0;945double denom = 0;946for (const Pair& pair : vert) {947smoothness += pair.first.smoothness;948smoothness += pair.second.smoothness;949denom += pair.first.smoothness == 0 ? 0 : 1;950denom += pair.second.smoothness == 0 ? 0 : 1;951}952smoothness /= denom;953954ForVert(vert[0].first.halfedge,955[this, &triIsFlatFace, smoothness](int current) {956if (!IsMarkedInsideQuad(current)) {957const int pair = halfedge_[current].pairedHalfedge;958SharpenTangent(current, triIsFlatFace[current / 3] ||959triIsFlatFace[pair / 3]960? 0961: smoothness);962}963});964}965});966967LinearizeFlatTangents();968DistributeTangents(fixedHalfedge);969}970971void Manifold::Impl::Refine(std::function<int(vec3, vec4, vec4)> edgeDivisions,972bool keepInterior) {973if (IsEmpty()) return;974Manifold::Impl old = *this;975Vec<Barycentric> vertBary = Subdivide(edgeDivisions, keepInterior);976if (vertBary.size() == 0) return;977978if (old.halfedgeTangent_.size() == old.halfedge_.size()) {979for_each_n(autoPolicy(NumTri(), 1e4), countAt(0), NumVert(),980InterpTri({vertPos_, vertBary, &old}));981}982983halfedgeTangent_.clear();984Finish();985if (old.halfedgeTangent_.size() == old.halfedge_.size()) {986MarkCoplanar();987}988meshRelation_.originalID = -1;989}990991} // namespace manifold992993994