godot-module-template/engine/thirdparty/manifold/src/smoothing.cpp

1004 lines
39 KiB
C++

// 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 "./impl.h"
#include "./parallel.h"
namespace {
using namespace manifold;
// Returns a normalized vector orthogonal to ref, in the plane of ref and in,
// unless in and ref are colinear, in which case it falls back to the plane of
// ref and altIn.
vec3 OrthogonalTo(vec3 in, vec3 altIn, vec3 ref) {
vec3 out = in - la::dot(in, ref) * ref;
if (la::dot(out, out) < kPrecision * la::dot(in, in)) {
out = altIn - la::dot(altIn, ref) * ref;
}
return SafeNormalize(out);
}
double Wrap(double radians) {
return radians < -kPi ? radians + kTwoPi
: radians > kPi ? radians - kTwoPi
: radians;
}
// Get the angle between two unit-vectors.
double AngleBetween(vec3 a, vec3 b) {
const double dot = la::dot(a, b);
return dot >= 1 ? 0 : (dot <= -1 ? kPi : la::acos(dot));
}
// Calculate a tangent vector in the form of a weighted cubic Bezier taking as
// input the desired tangent direction (length doesn't matter) and the edge
// vector to the neighboring vertex. In a symmetric situation where the tangents
// at each end are mirror images of each other, this will result in a circular
// arc.
vec4 CircularTangent(const vec3& tangent, const vec3& edgeVec) {
const vec3 dir = SafeNormalize(tangent);
double weight = std::max(0.5, la::dot(dir, SafeNormalize(edgeVec)));
// Quadratic weighted bezier for circular interpolation
const vec4 bz2 = vec4(dir * 0.5 * la::length(edgeVec), weight);
// Equivalent cubic weighted bezier
const vec4 bz3 = la::lerp(vec4(0, 0, 0, 1), bz2, 2 / 3.0);
// Convert from homogeneous form to geometric form
return vec4(vec3(bz3) / bz3.w, bz3.w);
}
struct InterpTri {
VecView<vec3> vertPos;
VecView<const Barycentric> vertBary;
const Manifold::Impl* impl;
static vec4 Homogeneous(vec4 v) {
v.x *= v.w;
v.y *= v.w;
v.z *= v.w;
return v;
}
static vec4 Homogeneous(vec3 v) { return vec4(v, 1.0); }
static vec3 HNormalize(vec4 v) {
return v.w == 0 ? vec3(v) : (vec3(v) / v.w);
}
static vec4 Scale(vec4 v, double scale) { return vec4(scale * vec3(v), v.w); }
static vec4 Bezier(vec3 point, vec4 tangent) {
return Homogeneous(vec4(point, 0) + tangent);
}
static mat4x2 CubicBezier2Linear(vec4 p0, vec4 p1, vec4 p2, vec4 p3,
double x) {
mat4x2 out;
vec4 p12 = la::lerp(p1, p2, x);
out[0] = la::lerp(la::lerp(p0, p1, x), p12, x);
out[1] = la::lerp(p12, la::lerp(p2, p3, x), x);
return out;
}
static vec3 BezierPoint(mat4x2 points, double x) {
return HNormalize(la::lerp(points[0], points[1], x));
}
static vec3 BezierTangent(mat4x2 points) {
return SafeNormalize(HNormalize(points[1]) - HNormalize(points[0]));
}
static vec3 RotateFromTo(vec3 v, quat start, quat end) {
return la::qrot(end, la::qrot(la::qconj(start), v));
}
static quat Slerp(const quat& x, const quat& y, double a, bool longWay) {
quat z = y;
double cosTheta = la::dot(x, y);
// Take the long way around the sphere only when requested
if ((cosTheta < 0) != longWay) {
z = -y;
cosTheta = -cosTheta;
}
if (cosTheta > 1.0 - std::numeric_limits<double>::epsilon()) {
return la::lerp(x, z, a); // for numerical stability
} else {
double angle = std::acos(cosTheta);
return (std::sin((1.0 - a) * angle) * x + std::sin(a * angle) * z) /
std::sin(angle);
}
}
static mat4x2 Bezier2Bezier(const mat3x2& corners, const mat4x2& tangentsX,
const mat4x2& tangentsY, double x,
const vec3& anchor) {
const mat4x2 bez = CubicBezier2Linear(
Homogeneous(corners[0]), Bezier(corners[0], tangentsX[0]),
Bezier(corners[1], tangentsX[1]), Homogeneous(corners[1]), x);
const vec3 end = BezierPoint(bez, x);
const vec3 tangent = BezierTangent(bez);
const mat3x2 nTangentsX(SafeNormalize(vec3(tangentsX[0])),
-SafeNormalize(vec3(tangentsX[1])));
const mat3x2 biTangents = {
OrthogonalTo(vec3(tangentsY[0]), (anchor - corners[0]), nTangentsX[0]),
OrthogonalTo(vec3(tangentsY[1]), (anchor - corners[1]), nTangentsX[1])};
const quat q0 = la::rotation_quat(mat3(
nTangentsX[0], biTangents[0], la::cross(nTangentsX[0], biTangents[0])));
const quat q1 = la::rotation_quat(mat3(
nTangentsX[1], biTangents[1], la::cross(nTangentsX[1], biTangents[1])));
const vec3 edge = corners[1] - corners[0];
const bool longWay =
la::dot(nTangentsX[0], edge) + la::dot(nTangentsX[1], edge) < 0;
const quat qTmp = Slerp(q0, q1, x, longWay);
const quat q = la::qmul(la::rotation_quat(la::qxdir(qTmp), tangent), qTmp);
const vec3 delta = la::lerp(RotateFromTo(vec3(tangentsY[0]), q0, q),
RotateFromTo(vec3(tangentsY[1]), q1, q), x);
const double deltaW = la::lerp(tangentsY[0].w, tangentsY[1].w, x);
return {Homogeneous(end), vec4(delta, deltaW)};
}
static vec3 Bezier2D(const mat3x4& corners, const mat4& tangentsX,
const mat4& tangentsY, double x, double y,
const vec3& centroid) {
mat4x2 bez0 =
Bezier2Bezier({corners[0], corners[1]}, {tangentsX[0], tangentsX[1]},
{tangentsY[0], tangentsY[1]}, x, centroid);
mat4x2 bez1 =
Bezier2Bezier({corners[2], corners[3]}, {tangentsX[2], tangentsX[3]},
{tangentsY[2], tangentsY[3]}, 1 - x, centroid);
const mat4x2 bez =
CubicBezier2Linear(bez0[0], Bezier(vec3(bez0[0]), bez0[1]),
Bezier(vec3(bez1[0]), bez1[1]), bez1[0], y);
return BezierPoint(bez, y);
}
void operator()(const int vert) {
vec3& pos = vertPos[vert];
const int tri = vertBary[vert].tri;
const vec4 uvw = vertBary[vert].uvw;
const ivec4 halfedges = impl->GetHalfedges(tri);
const mat3x4 corners = {
impl->vertPos_[impl->halfedge_[halfedges[0]].startVert],
impl->vertPos_[impl->halfedge_[halfedges[1]].startVert],
impl->vertPos_[impl->halfedge_[halfedges[2]].startVert],
halfedges[3] < 0
? vec3(0.0)
: impl->vertPos_[impl->halfedge_[halfedges[3]].startVert]};
for (const int i : {0, 1, 2, 3}) {
if (uvw[i] == 1) {
pos = corners[i];
return;
}
}
vec4 posH(0.0);
if (halfedges[3] < 0) { // tri
const mat4x3 tangentR = {impl->halfedgeTangent_[halfedges[0]],
impl->halfedgeTangent_[halfedges[1]],
impl->halfedgeTangent_[halfedges[2]]};
const mat4x3 tangentL = {
impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge],
impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],
impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge]};
const vec3 centroid = mat3(corners) * vec3(1.0 / 3);
for (const int i : {0, 1, 2}) {
const int j = Next3(i);
const int k = Prev3(i);
const double x = uvw[k] / (1 - uvw[i]);
const mat4x2 bez =
Bezier2Bezier({corners[j], corners[k]}, {tangentR[j], tangentL[k]},
{tangentL[j], tangentR[k]}, x, centroid);
const mat4x2 bez1 = CubicBezier2Linear(
bez[0], Bezier(vec3(bez[0]), bez[1]),
Bezier(corners[i], la::lerp(tangentR[i], tangentL[i], x)),
Homogeneous(corners[i]), uvw[i]);
const vec3 p = BezierPoint(bez1, uvw[i]);
posH += Homogeneous(vec4(p, uvw[j] * uvw[k]));
}
} else { // quad
const mat4 tangentsX = {
impl->halfedgeTangent_[halfedges[0]],
impl->halfedgeTangent_[impl->halfedge_[halfedges[0]].pairedHalfedge],
impl->halfedgeTangent_[halfedges[2]],
impl->halfedgeTangent_[impl->halfedge_[halfedges[2]].pairedHalfedge]};
const mat4 tangentsY = {
impl->halfedgeTangent_[impl->halfedge_[halfedges[3]].pairedHalfedge],
impl->halfedgeTangent_[halfedges[1]],
impl->halfedgeTangent_[impl->halfedge_[halfedges[1]].pairedHalfedge],
impl->halfedgeTangent_[halfedges[3]]};
const vec3 centroid = corners * vec4(0.25);
const double x = uvw[1] + uvw[2];
const double y = uvw[2] + uvw[3];
const vec3 pX = Bezier2D(corners, tangentsX, tangentsY, x, y, centroid);
const vec3 pY =
Bezier2D({corners[1], corners[2], corners[3], corners[0]},
{tangentsY[1], tangentsY[2], tangentsY[3], tangentsY[0]},
{tangentsX[1], tangentsX[2], tangentsX[3], tangentsX[0]}, y,
1 - x, centroid);
posH += Homogeneous(vec4(pX, x * (1 - x)));
posH += Homogeneous(vec4(pY, y * (1 - y)));
}
pos = HNormalize(posH);
}
};
} // namespace
namespace manifold {
/**
* Get the property normal associated with the startVert of this halfedge, where
* normalIdx shows the beginning of where normals are stored in the properties.
*/
vec3 Manifold::Impl::GetNormal(int halfedge, int normalIdx) const {
const int tri = halfedge / 3;
const int j = halfedge % 3;
const int prop = meshRelation_.triProperties[tri][j];
vec3 normal;
for (const int i : {0, 1, 2}) {
normal[i] =
meshRelation_.properties[prop * meshRelation_.numProp + normalIdx + i];
}
return normal;
}
/**
* Returns a circular tangent for the requested halfedge, orthogonal to the
* given normal vector, and avoiding folding.
*/
vec4 Manifold::Impl::TangentFromNormal(const vec3& normal, int halfedge) const {
const Halfedge edge = halfedge_[halfedge];
const vec3 edgeVec = vertPos_[edge.endVert] - vertPos_[edge.startVert];
const vec3 edgeNormal =
faceNormal_[halfedge / 3] + faceNormal_[edge.pairedHalfedge / 3];
vec3 dir = la::cross(la::cross(edgeNormal, edgeVec), normal);
return CircularTangent(dir, edgeVec);
}
/**
* Returns true if this halfedge should be marked as the interior of a quad, as
* defined by its two triangles referring to the same face, and those triangles
* having no further face neighbors beyond.
*/
bool Manifold::Impl::IsInsideQuad(int halfedge) const {
if (halfedgeTangent_.size() > 0) {
return halfedgeTangent_[halfedge].w < 0;
}
const int tri = halfedge / 3;
const TriRef ref = meshRelation_.triRef[tri];
const int pair = halfedge_[halfedge].pairedHalfedge;
const int pairTri = pair / 3;
const TriRef pairRef = meshRelation_.triRef[pairTri];
if (!ref.SameFace(pairRef)) return false;
auto SameFace = [this](int halfedge, const TriRef& ref) {
return ref.SameFace(
meshRelation_.triRef[halfedge_[halfedge].pairedHalfedge / 3]);
};
int neighbor = NextHalfedge(halfedge);
if (SameFace(neighbor, ref)) return false;
neighbor = NextHalfedge(neighbor);
if (SameFace(neighbor, ref)) return false;
neighbor = NextHalfedge(pair);
if (SameFace(neighbor, pairRef)) return false;
neighbor = NextHalfedge(neighbor);
if (SameFace(neighbor, pairRef)) return false;
return true;
}
/**
* Returns true if this halfedge is an interior of a quad, as defined by its
* halfedge tangent having negative weight.
*/
bool Manifold::Impl::IsMarkedInsideQuad(int halfedge) const {
return halfedgeTangent_.size() > 0 && halfedgeTangent_[halfedge].w < 0;
}
// sharpenedEdges are referenced to the input Mesh, but the triangles have
// been sorted in creating the Manifold, so the indices are converted using
// meshRelation_.
std::vector<Smoothness> Manifold::Impl::UpdateSharpenedEdges(
const std::vector<Smoothness>& sharpenedEdges) const {
std::unordered_map<int, int> oldHalfedge2New;
for (size_t tri = 0; tri < NumTri(); ++tri) {
int oldTri = meshRelation_.triRef[tri].tri;
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
}
std::vector<Smoothness> newSharp = sharpenedEdges;
for (Smoothness& edge : newSharp) {
edge.halfedge = oldHalfedge2New[edge.halfedge];
}
return newSharp;
}
// Find faces containing at least 3 triangles - these will not have
// interpolated normals - all their vert normals must match their face normal.
Vec<bool> Manifold::Impl::FlatFaces() const {
const int numTri = NumTri();
Vec<bool> triIsFlatFace(numTri, false);
for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,
[this, &triIsFlatFace](const int tri) {
const TriRef& ref = meshRelation_.triRef[tri];
int faceNeighbors = 0;
ivec3 faceTris = {-1, -1, -1};
for (const int j : {0, 1, 2}) {
const int neighborTri =
halfedge_[3 * tri + j].pairedHalfedge / 3;
const TriRef& jRef = meshRelation_.triRef[neighborTri];
if (jRef.SameFace(ref)) {
++faceNeighbors;
faceTris[j] = neighborTri;
}
}
if (faceNeighbors > 1) {
triIsFlatFace[tri] = true;
for (const int j : {0, 1, 2}) {
if (faceTris[j] >= 0) {
triIsFlatFace[faceTris[j]] = true;
}
}
}
});
return triIsFlatFace;
}
// Returns a vector of length numVert that has a tri that is part of a
// neighboring flat face if there is only one flat face. If there are none it
// gets -1, and if there are more than one it gets -2.
Vec<int> Manifold::Impl::VertFlatFace(const Vec<bool>& flatFaces) const {
Vec<int> vertFlatFace(NumVert(), -1);
Vec<TriRef> vertRef(NumVert(), {-1, -1, -1});
for (size_t tri = 0; tri < NumTri(); ++tri) {
if (flatFaces[tri]) {
for (const int j : {0, 1, 2}) {
const int vert = halfedge_[3 * tri + j].startVert;
if (vertRef[vert].SameFace(meshRelation_.triRef[tri])) continue;
vertRef[vert] = meshRelation_.triRef[tri];
vertFlatFace[vert] = vertFlatFace[vert] == -1 ? tri : -2;
}
}
}
return vertFlatFace;
}
Vec<int> Manifold::Impl::VertHalfedge() const {
Vec<int> vertHalfedge(NumVert());
Vec<uint8_t> counters(NumVert(), 0);
for_each_n(autoPolicy(halfedge_.size(), 1e5), countAt(0), halfedge_.size(),
[&vertHalfedge, &counters, this](const int idx) {
auto old = std::atomic_exchange(
reinterpret_cast<std::atomic<uint8_t>*>(
&counters[halfedge_[idx].startVert]),
static_cast<uint8_t>(1));
if (old == 1) return;
// arbitrary, last one wins.
vertHalfedge[halfedge_[idx].startVert] = idx;
});
return vertHalfedge;
}
std::vector<Smoothness> Manifold::Impl::SharpenEdges(
double minSharpAngle, double minSmoothness) const {
std::vector<Smoothness> sharpenedEdges;
const double minRadians = radians(minSharpAngle);
for (size_t e = 0; e < halfedge_.size(); ++e) {
if (!halfedge_[e].IsForward()) continue;
const size_t pair = halfedge_[e].pairedHalfedge;
const double dihedral =
std::acos(la::dot(faceNormal_[e / 3], faceNormal_[pair / 3]));
if (dihedral > minRadians) {
sharpenedEdges.push_back({e, minSmoothness});
sharpenedEdges.push_back({pair, minSmoothness});
}
}
return sharpenedEdges;
}
/**
* Sharpen tangents that intersect an edge to sharpen that edge. The weight is
* unchanged, as this has a squared effect on radius of curvature, except
* in the case of zero radius, which is marked with weight = 0.
*/
void Manifold::Impl::SharpenTangent(int halfedge, double smoothness) {
halfedgeTangent_[halfedge] =
vec4(smoothness * vec3(halfedgeTangent_[halfedge]),
smoothness == 0 ? 0 : halfedgeTangent_[halfedge].w);
}
/**
* Instead of calculating the internal shared normals like CalculateNormals
* does, this method fills in vertex properties, unshared across edges that
* are bent more than minSharpAngle.
*/
void Manifold::Impl::SetNormals(int normalIdx, double minSharpAngle) {
if (IsEmpty()) return;
if (normalIdx < 0) return;
const int oldNumProp = NumProp();
const int numTri = NumTri();
Vec<bool> triIsFlatFace = FlatFaces();
Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);
Vec<int> vertNumSharp(NumVert(), 0);
for (size_t e = 0; e < halfedge_.size(); ++e) {
if (!halfedge_[e].IsForward()) continue;
const int pair = halfedge_[e].pairedHalfedge;
const int tri1 = e / 3;
const int tri2 = pair / 3;
const double dihedral =
degrees(std::acos(la::dot(faceNormal_[tri1], faceNormal_[tri2])));
if (dihedral > minSharpAngle) {
++vertNumSharp[halfedge_[e].startVert];
++vertNumSharp[halfedge_[e].endVert];
} else {
const bool faceSplit =
triIsFlatFace[tri1] != triIsFlatFace[tri2] ||
(triIsFlatFace[tri1] && triIsFlatFace[tri2] &&
!meshRelation_.triRef[tri1].SameFace(meshRelation_.triRef[tri2]));
if (vertFlatFace[halfedge_[e].startVert] == -2 && faceSplit) {
++vertNumSharp[halfedge_[e].startVert];
}
if (vertFlatFace[halfedge_[e].endVert] == -2 && faceSplit) {
++vertNumSharp[halfedge_[e].endVert];
}
}
}
const int numProp = std::max(oldNumProp, normalIdx + 3);
Vec<double> oldProperties(numProp * NumPropVert(), 0);
meshRelation_.properties.swap(oldProperties);
meshRelation_.numProp = numProp;
if (meshRelation_.triProperties.size() == 0) {
meshRelation_.triProperties.resize(numTri);
for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri, [this](int tri) {
for (const int j : {0, 1, 2})
meshRelation_.triProperties[tri][j] = halfedge_[3 * tri + j].startVert;
});
}
Vec<ivec3> oldTriProp(numTri, {-1, -1, -1});
meshRelation_.triProperties.swap(oldTriProp);
for (int tri = 0; tri < numTri; ++tri) {
for (const int i : {0, 1, 2}) {
if (meshRelation_.triProperties[tri][i] >= 0) continue;
int startEdge = 3 * tri + i;
const int vert = halfedge_[startEdge].startVert;
if (vertNumSharp[vert] < 2) { // vertex has single normal
const vec3 normal = vertFlatFace[vert] >= 0
? faceNormal_[vertFlatFace[vert]]
: vertNormal_[vert];
int lastProp = -1;
ForVert(startEdge, [&](int current) {
const int thisTri = current / 3;
const int j = current - 3 * thisTri;
const int prop = oldTriProp[thisTri][j];
meshRelation_.triProperties[thisTri][j] = prop;
if (prop == lastProp) return;
lastProp = prop;
// update property vertex
auto start = oldProperties.begin() + prop * oldNumProp;
std::copy(start, start + oldNumProp,
meshRelation_.properties.begin() + prop * numProp);
for (const int i : {0, 1, 2})
meshRelation_.properties[prop * numProp + normalIdx + i] =
normal[i];
});
} else { // vertex has multiple normals
const vec3 centerPos = vertPos_[vert];
// Length degree
std::vector<int> group;
// Length number of normals
std::vector<vec3> normals;
int current = startEdge;
int prevFace = current / 3;
do { // find a sharp edge to start on
int next = NextHalfedge(halfedge_[current].pairedHalfedge);
const int face = next / 3;
const double dihedral = degrees(
std::acos(la::dot(faceNormal_[face], faceNormal_[prevFace])));
if (dihedral > minSharpAngle ||
triIsFlatFace[face] != triIsFlatFace[prevFace] ||
(triIsFlatFace[face] && triIsFlatFace[prevFace] &&
!meshRelation_.triRef[face].SameFace(
meshRelation_.triRef[prevFace]))) {
break;
}
current = next;
prevFace = face;
} while (current != startEdge);
const int endEdge = current;
struct FaceEdge {
int face;
vec3 edgeVec;
};
// calculate pseudo-normals between each sharp edge
ForVert<FaceEdge>(
endEdge,
[this, centerPos, &vertNumSharp, &vertFlatFace](int current) {
if (IsInsideQuad(current)) {
return FaceEdge({current / 3, vec3(NAN)});
}
const int vert = halfedge_[current].endVert;
vec3 pos = vertPos_[vert];
const vec3 edgeVec = centerPos - pos;
if (vertNumSharp[vert] < 2) {
// opposite vert has fixed normal
const vec3 normal = vertFlatFace[vert] >= 0
? faceNormal_[vertFlatFace[vert]]
: vertNormal_[vert];
// Flair out the normal we're calculating to give the edge a
// more constant curvature to meet the opposite normal. Achieve
// this by pointing the tangent toward the opposite bezier
// control point instead of the vert itself.
pos += vec3(TangentFromNormal(
normal, halfedge_[current].pairedHalfedge));
}
return FaceEdge({current / 3, SafeNormalize(pos - centerPos)});
},
[this, &triIsFlatFace, &normals, &group, minSharpAngle](
int current, const FaceEdge& here, FaceEdge& next) {
const double dihedral = degrees(std::acos(
la::dot(faceNormal_[here.face], faceNormal_[next.face])));
if (dihedral > minSharpAngle ||
triIsFlatFace[here.face] != triIsFlatFace[next.face] ||
(triIsFlatFace[here.face] && triIsFlatFace[next.face] &&
!meshRelation_.triRef[here.face].SameFace(
meshRelation_.triRef[next.face]))) {
normals.push_back(vec3(0.0));
}
group.push_back(normals.size() - 1);
if (std::isfinite(next.edgeVec.x)) {
normals.back() +=
SafeNormalize(la::cross(next.edgeVec, here.edgeVec)) *
AngleBetween(here.edgeVec, next.edgeVec);
} else {
next.edgeVec = here.edgeVec;
}
});
for (auto& normal : normals) {
normal = SafeNormalize(normal);
}
int lastGroup = 0;
int lastProp = -1;
int newProp = -1;
int idx = 0;
ForVert(endEdge, [&](int current1) {
const int thisTri = current1 / 3;
const int j = current1 - 3 * thisTri;
const int prop = oldTriProp[thisTri][j];
auto start = oldProperties.begin() + prop * oldNumProp;
if (group[idx] != lastGroup && group[idx] != 0 && prop == lastProp) {
// split property vertex, duplicating but with an updated normal
lastGroup = group[idx];
newProp = NumPropVert();
meshRelation_.properties.resize(meshRelation_.properties.size() +
numProp);
std::copy(start, start + oldNumProp,
meshRelation_.properties.begin() + newProp * numProp);
for (const int i : {0, 1, 2}) {
meshRelation_.properties[newProp * numProp + normalIdx + i] =
normals[group[idx]][i];
}
} else if (prop != lastProp) {
// update property vertex
lastProp = prop;
newProp = prop;
std::copy(start, start + oldNumProp,
meshRelation_.properties.begin() + prop * numProp);
for (const int i : {0, 1, 2})
meshRelation_.properties[prop * numProp + normalIdx + i] =
normals[group[idx]][i];
}
// point to updated property vertex
meshRelation_.triProperties[thisTri][j] = newProp;
++idx;
});
}
}
}
}
/**
* Tangents get flattened to create sharp edges by setting their weight to zero.
* This is the natural limit of reducing the weight to increase the sharpness
* smoothly. This limit gives a decent shape, but it causes the parameterization
* to be stretched and compresses it near the edges, which is good for resolving
* tight curvature, but bad for property interpolation. This function fixes the
* parameter stretch at the limit for sharp edges, since there is no curvature
* to resolve. Note this also changes the overall shape - making it more evenly
* curved.
*/
void Manifold::Impl::LinearizeFlatTangents() {
const int n = halfedgeTangent_.size();
for_each_n(autoPolicy(n, 1e4), countAt(0), n, [this](const int halfedge) {
vec4& tangent = halfedgeTangent_[halfedge];
vec4& otherTangent = halfedgeTangent_[halfedge_[halfedge].pairedHalfedge];
const bool flat[2] = {tangent.w == 0, otherTangent.w == 0};
if (!halfedge_[halfedge].IsForward() || (!flat[0] && !flat[1])) {
return;
}
const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -
vertPos_[halfedge_[halfedge].startVert];
if (flat[0] && flat[1]) {
tangent = vec4(edgeVec / 3.0, 1);
otherTangent = vec4(-edgeVec / 3.0, 1);
} else if (flat[0]) {
tangent = vec4((edgeVec + vec3(otherTangent)) / 2.0, 1);
} else {
otherTangent = vec4((-edgeVec + vec3(tangent)) / 2.0, 1);
}
});
}
/**
* Redistribute the tangents around each vertex so that the angles between them
* have the same ratios as the angles of the triangles between the corresponding
* edges. This avoids folding the output shape and gives smoother results. There
* must be at least one fixed halfedge on a vertex for that vertex to be
* operated on. If there is only one, then that halfedge is not treated as
* fixed, but the whole circle is turned to an average orientation.
*/
void Manifold::Impl::DistributeTangents(const Vec<bool>& fixedHalfedges) {
const int numHalfedge = fixedHalfedges.size();
for_each_n(
autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,
[this, &fixedHalfedges](int halfedge) {
if (!fixedHalfedges[halfedge]) return;
if (IsMarkedInsideQuad(halfedge)) {
halfedge = NextHalfedge(halfedge_[halfedge].pairedHalfedge);
}
vec3 normal(0.0);
Vec<double> currentAngle;
Vec<double> desiredAngle;
const vec3 approxNormal = vertNormal_[halfedge_[halfedge].startVert];
const vec3 center = vertPos_[halfedge_[halfedge].startVert];
vec3 lastEdgeVec =
SafeNormalize(vertPos_[halfedge_[halfedge].endVert] - center);
const vec3 firstTangent =
SafeNormalize(vec3(halfedgeTangent_[halfedge]));
vec3 lastTangent = firstTangent;
int current = halfedge;
do {
current = NextHalfedge(halfedge_[current].pairedHalfedge);
if (IsMarkedInsideQuad(current)) continue;
const vec3 thisEdgeVec =
SafeNormalize(vertPos_[halfedge_[current].endVert] - center);
const vec3 thisTangent =
SafeNormalize(vec3(halfedgeTangent_[current]));
normal += la::cross(thisTangent, lastTangent);
// cumulative sum
desiredAngle.push_back(
AngleBetween(thisEdgeVec, lastEdgeVec) +
(desiredAngle.size() > 0 ? desiredAngle.back() : 0));
if (current == halfedge) {
currentAngle.push_back(kTwoPi);
} else {
currentAngle.push_back(AngleBetween(thisTangent, firstTangent));
if (la::dot(approxNormal, la::cross(thisTangent, firstTangent)) <
0) {
currentAngle.back() = kTwoPi - currentAngle.back();
}
}
lastEdgeVec = thisEdgeVec;
lastTangent = thisTangent;
} while (!fixedHalfedges[current]);
if (currentAngle.size() == 1 || la::dot(normal, normal) == 0) return;
const double scale = currentAngle.back() / desiredAngle.back();
double offset = 0;
if (current == halfedge) { // only one - find average offset
for (size_t i = 0; i < currentAngle.size(); ++i) {
offset += Wrap(currentAngle[i] - scale * desiredAngle[i]);
}
offset /= currentAngle.size();
}
current = halfedge;
size_t i = 0;
do {
current = NextHalfedge(halfedge_[current].pairedHalfedge);
if (IsMarkedInsideQuad(current)) continue;
desiredAngle[i] *= scale;
const double lastAngle = i > 0 ? desiredAngle[i - 1] : 0;
// shrink obtuse angles
if (desiredAngle[i] - lastAngle > kPi) {
desiredAngle[i] = lastAngle + kPi;
} else if (i + 1 < desiredAngle.size() &&
scale * desiredAngle[i + 1] - desiredAngle[i] > kPi) {
desiredAngle[i] = scale * desiredAngle[i + 1] - kPi;
}
const double angle = currentAngle[i] - desiredAngle[i] - offset;
vec3 tangent(halfedgeTangent_[current]);
const quat q = la::rotation_quat(la::normalize(normal), angle);
halfedgeTangent_[current] =
vec4(la::qrot(q, tangent), halfedgeTangent_[current].w);
++i;
} while (!fixedHalfedges[current]);
});
}
/**
* Calculates halfedgeTangent_, allowing the manifold to be refined and
* smoothed. The tangents form weighted cubic Beziers along each edge. This
* function creates circular arcs where possible (minimizing maximum curvature),
* constrained to the indicated property normals. Across edges that form
* discontinuities in the normals, the tangent vectors are zero-length, allowing
* the shape to form a sharp corner with minimal oscillation.
*/
void Manifold::Impl::CreateTangents(int normalIdx) {
ZoneScoped;
const int numVert = NumVert();
const int numHalfedge = halfedge_.size();
halfedgeTangent_.resize(0);
Vec<vec4> tangent(numHalfedge);
Vec<bool> fixedHalfedge(numHalfedge, false);
Vec<int> vertHalfedge = VertHalfedge();
for_each_n(
autoPolicy(numVert, 1e4), vertHalfedge.begin(), numVert,
[this, &tangent, &fixedHalfedge, normalIdx](int e) {
struct FlatNormal {
bool isFlatFace;
vec3 normal;
};
ivec2 faceEdges(-1, -1);
ForVert<FlatNormal>(
e,
[normalIdx, this](int halfedge) {
const vec3 normal = GetNormal(halfedge, normalIdx);
const vec3 diff = faceNormal_[halfedge / 3] - normal;
return FlatNormal(
{la::dot(diff, diff) < kPrecision * kPrecision, normal});
},
[&faceEdges, &tangent, &fixedHalfedge, this](
int halfedge, const FlatNormal& here, const FlatNormal& next) {
if (IsInsideQuad(halfedge)) {
tangent[halfedge] = {0, 0, 0, -1};
return;
}
// mark special edges
const vec3 diff = next.normal - here.normal;
const bool differentNormals =
la::dot(diff, diff) > kPrecision * kPrecision;
if (differentNormals || here.isFlatFace != next.isFlatFace) {
fixedHalfedge[halfedge] = true;
if (faceEdges[0] == -1) {
faceEdges[0] = halfedge;
} else if (faceEdges[1] == -1) {
faceEdges[1] = halfedge;
} else {
faceEdges[0] = -2;
}
}
// calculate tangents
if (differentNormals) {
const vec3 edgeVec = vertPos_[halfedge_[halfedge].endVert] -
vertPos_[halfedge_[halfedge].startVert];
const vec3 dir = la::cross(here.normal, next.normal);
tangent[halfedge] = CircularTangent(
(la::dot(dir, edgeVec) < 0 ? -1.0 : 1.0) * dir, edgeVec);
} else {
tangent[halfedge] = TangentFromNormal(here.normal, halfedge);
}
});
if (faceEdges[0] >= 0 && faceEdges[1] >= 0) {
const vec3 edge0 = vertPos_[halfedge_[faceEdges[0]].endVert] -
vertPos_[halfedge_[faceEdges[0]].startVert];
const vec3 edge1 = vertPos_[halfedge_[faceEdges[1]].endVert] -
vertPos_[halfedge_[faceEdges[1]].startVert];
const vec3 newTangent = la::normalize(edge0) - la::normalize(edge1);
tangent[faceEdges[0]] = CircularTangent(newTangent, edge0);
tangent[faceEdges[1]] = CircularTangent(-newTangent, edge1);
} else if (faceEdges[0] == -1 && faceEdges[0] == -1) {
fixedHalfedge[e] = true;
}
});
halfedgeTangent_.swap(tangent);
DistributeTangents(fixedHalfedge);
}
/**
* Calculates halfedgeTangent_, allowing the manifold to be refined and
* smoothed. The tangents form weighted cubic Beziers along each edge. This
* function creates circular arcs where possible (minimizing maximum curvature),
* constrained to the vertex normals. Where sharpenedEdges are specified, the
* tangents are shortened that intersect the sharpened edge, concentrating the
* curvature there, while the tangents of the sharp edges themselves are aligned
* for continuity.
*/
void Manifold::Impl::CreateTangents(std::vector<Smoothness> sharpenedEdges) {
ZoneScoped;
const int numHalfedge = halfedge_.size();
halfedgeTangent_.resize(0);
Vec<vec4> tangent(numHalfedge);
Vec<bool> fixedHalfedge(numHalfedge, false);
Vec<int> vertHalfedge = VertHalfedge();
Vec<bool> triIsFlatFace = FlatFaces();
Vec<int> vertFlatFace = VertFlatFace(triIsFlatFace);
Vec<vec3> vertNormal = vertNormal_;
for (size_t v = 0; v < NumVert(); ++v) {
if (vertFlatFace[v] >= 0) {
vertNormal[v] = faceNormal_[vertFlatFace[v]];
}
}
for_each_n(autoPolicy(numHalfedge, 1e4), countAt(0), numHalfedge,
[&tangent, &vertNormal, this](const int edgeIdx) {
tangent[edgeIdx] =
IsInsideQuad(edgeIdx)
? vec4(0, 0, 0, -1)
: TangentFromNormal(
vertNormal[halfedge_[edgeIdx].startVert], edgeIdx);
});
halfedgeTangent_.swap(tangent);
// Add sharpened edges around faces, just on the face side.
for (size_t tri = 0; tri < NumTri(); ++tri) {
if (!triIsFlatFace[tri]) continue;
for (const int j : {0, 1, 2}) {
const int tri2 = halfedge_[3 * tri + j].pairedHalfedge / 3;
if (!triIsFlatFace[tri2] ||
!meshRelation_.triRef[tri].SameFace(meshRelation_.triRef[tri2])) {
sharpenedEdges.push_back({3 * tri + j, 0});
}
}
}
using Pair = std::pair<Smoothness, Smoothness>;
// Fill in missing pairs with default smoothness = 1.
std::map<int, Pair> edges;
for (Smoothness edge : sharpenedEdges) {
if (edge.smoothness >= 1) continue;
const bool forward = halfedge_[edge.halfedge].IsForward();
const int pair = halfedge_[edge.halfedge].pairedHalfedge;
const int idx = forward ? edge.halfedge : pair;
if (edges.find(idx) == edges.end()) {
edges[idx] = {edge, {static_cast<size_t>(pair), 1}};
if (!forward) std::swap(edges[idx].first, edges[idx].second);
} else {
Smoothness& e = forward ? edges[idx].first : edges[idx].second;
e.smoothness = std::min(edge.smoothness, e.smoothness);
}
}
std::map<int, std::vector<Pair>> vertTangents;
for (const auto& value : edges) {
const Pair edge = value.second;
vertTangents[halfedge_[edge.first.halfedge].startVert].push_back(edge);
vertTangents[halfedge_[edge.second.halfedge].startVert].push_back(
{edge.second, edge.first});
}
const int numVert = NumVert();
for_each_n(
autoPolicy(numVert, 1e4), countAt(0), numVert,
[this, &vertTangents, &fixedHalfedge, &vertHalfedge,
&triIsFlatFace](int v) {
auto it = vertTangents.find(v);
if (it == vertTangents.end()) {
fixedHalfedge[vertHalfedge[v]] = true;
return;
}
const std::vector<Pair>& vert = it->second;
// Sharp edges that end are smooth at their terminal vert.
if (vert.size() == 1) return;
if (vert.size() == 2) { // Make continuous edge
const int first = vert[0].first.halfedge;
const int second = vert[1].first.halfedge;
fixedHalfedge[first] = true;
fixedHalfedge[second] = true;
const vec3 newTangent = la::normalize(vec3(halfedgeTangent_[first]) -
vec3(halfedgeTangent_[second]));
const vec3 pos = vertPos_[halfedge_[first].startVert];
halfedgeTangent_[first] = CircularTangent(
newTangent, vertPos_[halfedge_[first].endVert] - pos);
halfedgeTangent_[second] = CircularTangent(
-newTangent, vertPos_[halfedge_[second].endVert] - pos);
double smoothness =
(vert[0].second.smoothness + vert[1].first.smoothness) / 2;
ForVert(first, [this, &smoothness, &vert, first,
second](int current) {
if (current == second) {
smoothness =
(vert[1].second.smoothness + vert[0].first.smoothness) / 2;
} else if (current != first && !IsMarkedInsideQuad(current)) {
SharpenTangent(current, smoothness);
}
});
} else { // Sharpen vertex uniformly
double smoothness = 0;
double denom = 0;
for (const Pair& pair : vert) {
smoothness += pair.first.smoothness;
smoothness += pair.second.smoothness;
denom += pair.first.smoothness == 0 ? 0 : 1;
denom += pair.second.smoothness == 0 ? 0 : 1;
}
smoothness /= denom;
ForVert(vert[0].first.halfedge,
[this, &triIsFlatFace, smoothness](int current) {
if (!IsMarkedInsideQuad(current)) {
const int pair = halfedge_[current].pairedHalfedge;
SharpenTangent(current, triIsFlatFace[current / 3] ||
triIsFlatFace[pair / 3]
? 0
: smoothness);
}
});
}
});
LinearizeFlatTangents();
DistributeTangents(fixedHalfedge);
}
void Manifold::Impl::Refine(std::function<int(vec3, vec4, vec4)> edgeDivisions,
bool keepInterior) {
if (IsEmpty()) return;
Manifold::Impl old = *this;
Vec<Barycentric> vertBary = Subdivide(edgeDivisions, keepInterior);
if (vertBary.size() == 0) return;
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
for_each_n(autoPolicy(NumTri(), 1e4), countAt(0), NumVert(),
InterpTri({vertPos_, vertBary, &old}));
}
halfedgeTangent_.resize(0);
Finish();
CreateFaces();
meshRelation_.originalID = -1;
}
} // namespace manifold