PrusaSlicer-NonPlainar/src/libslic3r/MeshNormals.cpp

164 lines
6.2 KiB
C++

#include "MeshNormals.hpp"
#include <numeric>
#include <boost/container/small_vector.hpp>
namespace Slic3r {
static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double epsSq = 0.05)
{
using Line3D = Eigen::ParametrizedLine<double, 3>;
auto line = Line3D::Through(e1, e2);
return line.squaredDistance(p) < epsSq;
}
Vec3d get_normal(const AABBMesh &mesh,
const Vec3d &picking_point,
double eps)
{
Vec3d ret = Vec3d::Zero();
int faceid = 0;
Vec3d p;
mesh.squared_distance(picking_point, faceid, p);
assert(int(faceid) < int(mesh.get_triangle_mesh()->indices.size()));
auto trindex = mesh.indices(faceid);
const Vec3d &p1 = mesh.vertices(trindex(0)).cast<double>();
const Vec3d &p2 = mesh.vertices(trindex(1)).cast<double>();
const Vec3d &p3 = mesh.vertices(trindex(2)).cast<double>();
// We should check if the point lies on an edge of the hosting
// triangle. If it does then all the other triangles using the
// same two points have to be searched and the final normal should
// be some kind of aggregation of the participating triangle
// normals. We should also consider the cases where the support
// point lies right on a vertex of its triangle. The procedure is
// the same, get the neighbor triangles and calculate an average
// normal.
// Mark the vertex indices of the edge. ia and ib marks an edge.
// ic will mark a single vertex.
int vertex_idx = -1;
int edge_idx = -1;
double epsSq = eps * eps;
if ((p - p1).squaredNorm() < epsSq) {
vertex_idx = trindex(0);
} else if ((p - p2).squaredNorm() < epsSq) {
vertex_idx = trindex(1);
} else if ((p - p3).squaredNorm() < epsSq) {
vertex_idx = trindex(2);
} else if (point_on_edge(p, p1, p2, epsSq)) {
edge_idx = 0;
} else if (point_on_edge(p, p2, p3, epsSq)) {
edge_idx = 1;
} else if (point_on_edge(p, p1, p3, epsSq)) {
edge_idx = 2;
}
// vector for the neigboring triangles including the detected one.
constexpr size_t MAX_EXPECTED_NEIGHBORS = 10;
boost::container::small_vector<Vec3d, MAX_EXPECTED_NEIGHBORS> neigh;
auto &vfidx = mesh.vertex_face_index();
auto cmpfn = [](const Vec3d &v1, const Vec3d &v2) { return v1.sum() < v2.sum(); };
auto eqfn = [](const Vec3d &n1, const Vec3d &n2) {
// Compare normals for equivalence.
// This is controvers stuff.
auto deq = [](double a, double b) {
return std::abs(a - b) < 1e-3;
};
return deq(n1(X), n2(X)) &&
deq(n1(Y), n2(Y)) &&
deq(n1(Z), n2(Z));
};
if (vertex_idx >= 0) { // The point is right on a vertex of the triangle
neigh.reserve(vfidx.count(vertex_idx));
auto from = vfidx.begin(vertex_idx);
auto to = vfidx.end(vertex_idx);
for (auto it = from; it != to; ++it) {
Vec3d nrm = mesh.normal_by_face_id(*it);
auto oit = std::lower_bound(neigh.begin(), neigh.end(), nrm, cmpfn);
if (oit == neigh.end() || !eqfn(*oit, nrm))
neigh.insert(oit, mesh.normal_by_face_id(*it));
}
} else if (edge_idx >= 0) { // the point is on and edge
size_t neighbor_face = mesh.face_neighbor_index()[faceid](edge_idx);
if (neighbor_face < mesh.indices().size()) {
neigh.emplace_back(mesh.normal_by_face_id(faceid));
neigh.emplace_back(mesh.normal_by_face_id(neighbor_face));
}
}
if (!neigh.empty()) { // there were neighbors to count with
// sum up the normals and then normalize the result again.
// This unification seems to be enough.
Vec3d sumnorm(0, 0, 0);
sumnorm = std::accumulate(neigh.begin(), neigh.end(), sumnorm);
sumnorm.normalize();
ret = sumnorm;
} else { // point lies safely within its triangle
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
ret = U.cross(V).normalized();
}
return ret;
}
template<class Ex>
Eigen::MatrixXd normals(Ex ex_policy,
const PointSet &points,
const AABBMesh &mesh,
double eps,
std::function<void()> thr, // throw on cancel
const std::vector<unsigned> &pt_indices)
{
if (points.rows() == 0 || mesh.vertices().empty() ||
mesh.indices().empty())
return {};
std::vector<unsigned> range = pt_indices;
if (range.empty()) {
range.resize(size_t(points.rows()), 0);
std::iota(range.begin(), range.end(), 0);
}
PointSet ret(range.size(), 3);
execution::for_each(ex_policy, size_t(0), range.size(),
[&ret, &mesh, &points, thr, eps, &range](size_t ridx) {
thr();
unsigned el = range[ridx];
auto eidx = Eigen::Index(el);
auto picking_point = points.row(eidx);
ret.row(ridx) = get_normal(mesh, picking_point, eps);
});
return ret;
}
template Eigen::MatrixXd normals(ExecutionSeq policy,
const PointSet &points,
const AABBMesh &convert_mesh,
double eps,
std::function<void()> throw_on_cancel,
const std::vector<unsigned> &selected_points);
template Eigen::MatrixXd normals(ExecutionTBB policy,
const PointSet &points,
const AABBMesh &convert_mesh,
double eps,
std::function<void()> throw_on_cancel,
const std::vector<unsigned> &selected_points);
} // namespace Slic3r