Merge branch 'tm_hollowing'
This commit is contained in:
commit
fd11dcd44b
@ -252,46 +252,6 @@ template<class T> struct remove_cvref
|
|||||||
|
|
||||||
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
||||||
|
|
||||||
template<class T> using DefaultContainer = std::vector<T>;
|
|
||||||
|
|
||||||
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
|
||||||
template<class T, class I, template<class> class Container = DefaultContainer>
|
|
||||||
inline Container<remove_cvref_t<T>> linspace(const T &start,
|
|
||||||
const T &stop,
|
|
||||||
const I &n)
|
|
||||||
{
|
|
||||||
Container<remove_cvref_t<T>> vals(n, T());
|
|
||||||
|
|
||||||
T stride = (stop - start) / n;
|
|
||||||
size_t i = 0;
|
|
||||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
|
||||||
return start + i++ * stride;
|
|
||||||
});
|
|
||||||
|
|
||||||
return vals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of equidistant values starting from 'start' (inclusive), ending
|
|
||||||
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
|
||||||
/// leaving 'stride' space between each value.
|
|
||||||
/// Very similar to Matlab [start:stride:end] notation.
|
|
||||||
template<class T, template<class> class Container = DefaultContainer>
|
|
||||||
inline Container<remove_cvref_t<T>> grid(const T &start,
|
|
||||||
const T &stop,
|
|
||||||
const T &stride)
|
|
||||||
{
|
|
||||||
Container<remove_cvref_t<T>>
|
|
||||||
vals(size_t(std::ceil((stop - start) / stride)), T());
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
|
||||||
return start + i++ * stride;
|
|
||||||
});
|
|
||||||
|
|
||||||
return vals;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// A shorter C++14 style form of the enable_if metafunction
|
// A shorter C++14 style form of the enable_if metafunction
|
||||||
template<bool B, class T>
|
template<bool B, class T>
|
||||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||||
@ -392,6 +352,56 @@ inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
||||||
|
template<class T, class I>
|
||||||
|
inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,
|
||||||
|
const T &stop,
|
||||||
|
const IntegerOnly<I> &n)
|
||||||
|
{
|
||||||
|
std::vector<T> vals(n, T());
|
||||||
|
|
||||||
|
T stride = (stop - start) / n;
|
||||||
|
size_t i = 0;
|
||||||
|
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||||
|
return start + i++ * stride;
|
||||||
|
});
|
||||||
|
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N, class T>
|
||||||
|
inline std::array<ArithmeticOnly<T>, N> linspace_array(const T &start, const T &stop)
|
||||||
|
{
|
||||||
|
std::array<T, N> vals = {T()};
|
||||||
|
|
||||||
|
T stride = (stop - start) / N;
|
||||||
|
size_t i = 0;
|
||||||
|
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||||
|
return start + i++ * stride;
|
||||||
|
});
|
||||||
|
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of equidistant values starting from 'start' (inclusive), ending
|
||||||
|
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
||||||
|
/// leaving 'stride' space between each value.
|
||||||
|
/// Very similar to Matlab [start:stride:end] notation.
|
||||||
|
template<class T>
|
||||||
|
inline std::vector<ArithmeticOnly<T>> grid(const T &start,
|
||||||
|
const T &stop,
|
||||||
|
const T &stride)
|
||||||
|
{
|
||||||
|
std::vector<T> vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||||
|
return start + i++ * stride;
|
||||||
|
});
|
||||||
|
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // MTUTILS_HPP
|
#endif // MTUTILS_HPP
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <igl/ray_mesh_intersect.h>
|
#include <igl/ray_mesh_intersect.h>
|
||||||
#include <igl/point_mesh_squared_distance.h>
|
#include <igl/point_mesh_squared_distance.h>
|
||||||
#include <igl/remove_duplicate_vertices.h>
|
#include <igl/remove_duplicate_vertices.h>
|
||||||
|
#include <igl/collapse_small_triangles.h>
|
||||||
#include <igl/signed_distance.h>
|
#include <igl/signed_distance.h>
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
@ -194,17 +195,12 @@ public:
|
|||||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
};
|
};
|
||||||
|
|
||||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
static const constexpr double MESH_EPS = 1e-6;
|
||||||
static const double dEPS = 1e-6;
|
|
||||||
|
void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F)
|
||||||
|
{
|
||||||
const stl_file& stl = tmesh.stl;
|
const stl_file& stl = tmesh.stl;
|
||||||
|
|
||||||
auto&& bb = tmesh.bounding_box();
|
|
||||||
m_ground_level += bb.min(Z);
|
|
||||||
|
|
||||||
Eigen::MatrixXd V;
|
|
||||||
Eigen::MatrixXi F;
|
|
||||||
|
|
||||||
V.resize(3*stl.stats.number_of_facets, 3);
|
V.resize(3*stl.stats.number_of_facets, 3);
|
||||||
F.resize(stl.stats.number_of_facets, 3);
|
F.resize(stl.stats.number_of_facets, 3);
|
||||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||||
@ -217,9 +213,37 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
|||||||
F(i, 2) = int(3*i+2);
|
F(i, 2) = int(3*i+2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will convert this to a proper 3d mesh with no duplicate points.
|
if (!tmesh.has_shared_vertices())
|
||||||
Eigen::VectorXi SVI, SVJ;
|
{
|
||||||
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
|
Eigen::MatrixXd rV;
|
||||||
|
Eigen::MatrixXi rF;
|
||||||
|
// We will convert this to a proper 3d mesh with no duplicate points.
|
||||||
|
Eigen::VectorXi SVI, SVJ;
|
||||||
|
igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF);
|
||||||
|
V = std::move(rV);
|
||||||
|
F = std::move(rF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out)
|
||||||
|
{
|
||||||
|
Pointf3s points(size_t(V.rows()));
|
||||||
|
std::vector<Vec3crd> facets(size_t(F.rows()));
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < V.rows(); ++i)
|
||||||
|
points[size_t(i)] = V.row(i);
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < F.rows(); ++i)
|
||||||
|
facets[size_t(i)] = F.row(i);
|
||||||
|
|
||||||
|
out = {points, facets};
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
||||||
|
auto&& bb = tmesh.bounding_box();
|
||||||
|
m_ground_level += bb.min(Z);
|
||||||
|
|
||||||
|
to_eigen_mesh(tmesh, m_V, m_F);
|
||||||
|
|
||||||
// Build the AABB accelaration tree
|
// Build the AABB accelaration tree
|
||||||
m_aabb->init(m_V, m_F);
|
m_aabb->init(m_V, m_F);
|
||||||
@ -262,6 +286,10 @@ EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
|||||||
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
|
||||||
|
|
||||||
EigenMesh3D::hit_result
|
EigenMesh3D::hit_result
|
||||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,9 @@ namespace sla {
|
|||||||
|
|
||||||
struct Contour3D;
|
struct Contour3D;
|
||||||
|
|
||||||
|
void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F);
|
||||||
|
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &);
|
||||||
|
|
||||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||||
/// alternative (raw) input format for the SLASupportTree.
|
/// alternative (raw) input format for the SLASupportTree.
|
||||||
// Implemented in libslic3r/SLA/Common.cpp
|
// Implemented in libslic3r/SLA/Common.cpp
|
||||||
@ -30,11 +33,15 @@ class EigenMesh3D {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
EigenMesh3D(const TriangleMesh&);
|
explicit EigenMesh3D(const TriangleMesh&);
|
||||||
|
explicit EigenMesh3D(const Contour3D &other);
|
||||||
|
|
||||||
EigenMesh3D(const EigenMesh3D& other);
|
EigenMesh3D(const EigenMesh3D& other);
|
||||||
EigenMesh3D(const Contour3D &other);
|
|
||||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||||
|
|
||||||
|
EigenMesh3D(EigenMesh3D &&other);
|
||||||
|
EigenMesh3D& operator=(EigenMesh3D &&other);
|
||||||
|
|
||||||
~EigenMesh3D();
|
~EigenMesh3D();
|
||||||
|
|
||||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||||
@ -70,9 +77,6 @@ public:
|
|||||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||||
inline bool is_hit() const { return !std::isinf(m_t); }
|
inline bool is_hit() const { return !std::isinf(m_t); }
|
||||||
|
|
||||||
// Hit_result can decay into a double as the hit distance.
|
|
||||||
inline operator double() const { return distance(); }
|
|
||||||
|
|
||||||
inline const Vec3d& normal() const {
|
inline const Vec3d& normal() const {
|
||||||
assert(is_valid());
|
assert(is_valid());
|
||||||
return m_normal;
|
return m_normal;
|
||||||
|
@ -166,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
|||||||
return pc == ABORT;
|
return pc == ABORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give points on a 3D ring with given center, radius and orientation
|
||||||
|
// method based on:
|
||||||
|
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||||
|
template<size_t N>
|
||||||
|
class PointRing {
|
||||||
|
std::array<double, N> m_phis;
|
||||||
|
|
||||||
|
// Two vectors that will be perpendicular to each other and to the
|
||||||
|
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||||
|
// placeholder.
|
||||||
|
// a and b vectors are perpendicular to the ring direction and to each other.
|
||||||
|
// Together they define the plane where we have to iterate with the
|
||||||
|
// given angles in the 'm_phis' vector
|
||||||
|
Vec3d a = {0, 1, 0}, b;
|
||||||
|
double m_radius = 0.;
|
||||||
|
|
||||||
|
static inline bool constexpr is_one(double val)
|
||||||
|
{
|
||||||
|
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
PointRing(const Vec3d &n)
|
||||||
|
{
|
||||||
|
m_phis = linspace_array<N>(0., 2 * PI);
|
||||||
|
|
||||||
|
// We have to address the case when the direction vector v (same as
|
||||||
|
// dir) is coincident with one of the world axes. In this case two of
|
||||||
|
// its components will be completely zero and one is 1.0. Our method
|
||||||
|
// becomes dangerous here due to division with zero. Instead, vector
|
||||||
|
// 'a' can be an element-wise rotated version of 'v'
|
||||||
|
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
||||||
|
a = {n(Z), n(X), n(Y)};
|
||||||
|
b = {n(Y), n(Z), n(X)};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
||||||
|
b = a.cross(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d get(size_t idx, const Vec3d src, double r) const
|
||||||
|
{
|
||||||
|
double phi = m_phis[idx];
|
||||||
|
double sinphi = std::sin(phi);
|
||||||
|
double cosphi = std::cos(phi);
|
||||||
|
|
||||||
|
double rpscos = r * cosphi;
|
||||||
|
double rpssin = r * sinphi;
|
||||||
|
|
||||||
|
// Point on the sphere
|
||||||
|
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
||||||
|
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||||
|
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C, class Hit = EigenMesh3D::hit_result>
|
||||||
|
static Hit min_hit(const C &hits)
|
||||||
|
{
|
||||||
|
auto mit = std::min_element(hits.begin(), hits.end(),
|
||||||
|
[](const Hit &h1, const Hit &h2) {
|
||||||
|
return h1.distance() < h2.distance();
|
||||||
|
});
|
||||||
|
|
||||||
|
return *mit;
|
||||||
|
}
|
||||||
|
|
||||||
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
||||||
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
||||||
{
|
{
|
||||||
static const size_t SAMPLES = 8;
|
static const size_t SAMPLES = 8;
|
||||||
|
|
||||||
// method based on:
|
// Move away slightly from the touching point to avoid raycasting on the
|
||||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
// inner surface of the mesh.
|
||||||
|
|
||||||
|
const double& sd = m_cfg.safety_distance_mm;
|
||||||
|
|
||||||
|
auto& m = m_mesh;
|
||||||
|
using HitResult = EigenMesh3D::hit_result;
|
||||||
|
|
||||||
|
// Hit results
|
||||||
|
std::array<HitResult, SAMPLES> hits;
|
||||||
|
|
||||||
|
struct Rings {
|
||||||
|
double rpin;
|
||||||
|
double rback;
|
||||||
|
Vec3d spin;
|
||||||
|
Vec3d sback;
|
||||||
|
PointRing<SAMPLES> ring;
|
||||||
|
|
||||||
|
Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
|
||||||
|
Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
|
||||||
|
} rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
|
||||||
|
|
||||||
// We will shoot multiple rays from the head pinpoint in the direction
|
// We will shoot multiple rays from the head pinpoint in the direction
|
||||||
// of the pinhead robe (side) surface. The result will be the smallest
|
// of the pinhead robe (side) surface. The result will be the smallest
|
||||||
// hit distance.
|
// hit distance.
|
||||||
|
|
||||||
// Move away slightly from the touching point to avoid raycasting on the
|
ccr::enumerate(hits.begin(), hits.end(),
|
||||||
// inner surface of the mesh.
|
[&m, &rings, sd](HitResult &hit, size_t i) {
|
||||||
Vec3d v = dir; // Our direction (axis)
|
|
||||||
Vec3d c = s + width * dir;
|
|
||||||
const double& sd = m_cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
// Two vectors that will be perpendicular to each other and to the
|
// Point on the circle on the pin sphere
|
||||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
Vec3d ps = rings.pinring(i);
|
||||||
// placeholder.
|
// This is the point on the circle on the back sphere
|
||||||
Vec3d a(0, 1, 0), b;
|
Vec3d p = rings.backring(i);
|
||||||
|
|
||||||
|
// Point ps is not on mesh but can be inside or
|
||||||
|
// outside as well. This would cause many problems
|
||||||
|
// with ray-casting. To detect the position we will
|
||||||
|
// use the ray-casting result (which has an is_inside
|
||||||
|
// predicate).
|
||||||
|
|
||||||
// The portions of the circle (the head-back circle) for which we will
|
Vec3d n = (p - ps).normalized();
|
||||||
// shoot rays.
|
auto q = m.query_ray_hit(ps + sd * n, n);
|
||||||
std::array<double, SAMPLES> phis;
|
|
||||||
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
|
||||||
|
|
||||||
auto& m = m_mesh;
|
if (q.is_inside()) { // the hit is inside the model
|
||||||
using HitResult = EigenMesh3D::hit_result;
|
if (q.distance() > rings.rpin) {
|
||||||
|
// If we are inside the model and the hit
|
||||||
|
// distance is bigger than our pin circle
|
||||||
|
// diameter, it probably indicates that the
|
||||||
|
// support point was already inside the
|
||||||
|
// model, or there is really no space
|
||||||
|
// around the point. We will assign a zero
|
||||||
|
// hit distance to these cases which will
|
||||||
|
// enforce the function return value to be
|
||||||
|
// an invalid ray with zero hit distance.
|
||||||
|
// (see min_element at the end)
|
||||||
|
hit = HitResult(0.0);
|
||||||
|
} else {
|
||||||
|
// re-cast the ray from the outside of the
|
||||||
|
// object. The starting point has an offset
|
||||||
|
// of 2*safety_distance because the
|
||||||
|
// original ray has also had an offset
|
||||||
|
auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
|
||||||
|
hit = q2;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
hit = q;
|
||||||
|
});
|
||||||
|
|
||||||
// Hit results
|
return min_hit(hits);
|
||||||
std::array<HitResult, SAMPLES> hits;
|
|
||||||
|
|
||||||
// We have to address the case when the direction vector v (same as
|
|
||||||
// dir) is coincident with one of the world axes. In this case two of
|
|
||||||
// its components will be completely zero and one is 1.0. Our method
|
|
||||||
// becomes dangerous here due to division with zero. Instead, vector
|
|
||||||
// 'a' can be an element-wise rotated version of 'v'
|
|
||||||
auto chk1 = [] (double val) {
|
|
||||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) {
|
|
||||||
a = {v(Z), v(X), v(Y)};
|
|
||||||
b = {v(Y), v(Z), v(X)};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize();
|
|
||||||
b = a.cross(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now a and b vectors are perpendicular to v and to each other.
|
|
||||||
// Together they define the plane where we have to iterate with the
|
|
||||||
// given angles in the 'phis' vector
|
|
||||||
ccr::enumerate(
|
|
||||||
phis.begin(), phis.end(),
|
|
||||||
[&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) {
|
|
||||||
double sinphi = std::sin(phi);
|
|
||||||
double cosphi = std::cos(phi);
|
|
||||||
|
|
||||||
// Let's have a safety coefficient for the radiuses.
|
|
||||||
double rpscos = (sd + r_pin) * cosphi;
|
|
||||||
double rpssin = (sd + r_pin) * sinphi;
|
|
||||||
double rpbcos = (sd + r_back) * cosphi;
|
|
||||||
double rpbsin = (sd + r_back) * sinphi;
|
|
||||||
|
|
||||||
// Point on the circle on the pin sphere
|
|
||||||
Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X),
|
|
||||||
s(Y) + rpscos * a(Y) + rpssin * b(Y),
|
|
||||||
s(Z) + rpscos * a(Z) + rpssin * b(Z));
|
|
||||||
|
|
||||||
// Point ps is not on mesh but can be inside or
|
|
||||||
// outside as well. This would cause many problems
|
|
||||||
// with ray-casting. To detect the position we will
|
|
||||||
// use the ray-casting result (which has an is_inside
|
|
||||||
// predicate).
|
|
||||||
|
|
||||||
// This is the point on the circle on the back sphere
|
|
||||||
Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X),
|
|
||||||
c(Y) + rpbcos * a(Y) + rpbsin * b(Y),
|
|
||||||
c(Z) + rpbcos * a(Z) + rpbsin * b(Z));
|
|
||||||
|
|
||||||
Vec3d n = (p - ps).normalized();
|
|
||||||
auto q = m.query_ray_hit(ps + sd * n, n);
|
|
||||||
|
|
||||||
if (q.is_inside()) { // the hit is inside the model
|
|
||||||
if (q.distance() > r_pin + sd) {
|
|
||||||
// If we are inside the model and the hit
|
|
||||||
// distance is bigger than our pin circle
|
|
||||||
// diameter, it probably indicates that the
|
|
||||||
// support point was already inside the
|
|
||||||
// model, or there is really no space
|
|
||||||
// around the point. We will assign a zero
|
|
||||||
// hit distance to these cases which will
|
|
||||||
// enforce the function return value to be
|
|
||||||
// an invalid ray with zero hit distance.
|
|
||||||
// (see min_element at the end)
|
|
||||||
hits[i] = HitResult(0.0);
|
|
||||||
} else {
|
|
||||||
// re-cast the ray from the outside of the
|
|
||||||
// object. The starting point has an offset
|
|
||||||
// of 2*safety_distance because the
|
|
||||||
// original ray has also had an offset
|
|
||||||
auto q2 = m.query_ray_hit(
|
|
||||||
ps + (q.distance() + 2 * sd) * n, n);
|
|
||||||
hits[i] = q2;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
hits[i] = q;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto mit = std::min_element(hits.begin(), hits.end());
|
|
||||||
|
|
||||||
return *mit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
||||||
const Vec3d &s, const Vec3d &dir, double r, bool ins_check)
|
const Vec3d &src, const Vec3d &dir, double r, bool ins_check)
|
||||||
{
|
{
|
||||||
static const size_t SAMPLES = 8;
|
static const size_t SAMPLES = 8;
|
||||||
|
PointRing<SAMPLES> ring{dir};
|
||||||
|
|
||||||
// helper vector calculations
|
using Hit = EigenMesh3D::hit_result;
|
||||||
Vec3d a(0, 1, 0), b;
|
|
||||||
const double& sd = m_cfg.safety_distance_mm;
|
|
||||||
|
|
||||||
// INFO: for explanation of the method used here, see the previous
|
|
||||||
// method's comments.
|
|
||||||
|
|
||||||
auto chk1 = [] (double val) {
|
|
||||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) {
|
|
||||||
a = {dir(Z), dir(X), dir(Y)};
|
|
||||||
b = {dir(Y), dir(Z), dir(X)};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize();
|
|
||||||
b = a.cross(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// circle portions
|
|
||||||
std::array<double, SAMPLES> phis;
|
|
||||||
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
|
||||||
|
|
||||||
auto& m = m_mesh;
|
|
||||||
using HitResult = EigenMesh3D::hit_result;
|
|
||||||
|
|
||||||
// Hit results
|
// Hit results
|
||||||
std::array<HitResult, SAMPLES> hits;
|
std::array<Hit, SAMPLES> hits;
|
||||||
|
|
||||||
ccr::enumerate(
|
ccr::enumerate(hits.begin(), hits.end(),
|
||||||
phis.begin(), phis.end(),
|
[this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) {
|
||||||
[&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) {
|
|
||||||
double sinphi = std::sin(phi);
|
const double sd = m_cfg.safety_distance_mm;
|
||||||
double cosphi = std::cos(phi);
|
|
||||||
|
// Point on the circle on the pin sphere
|
||||||
// Let's have a safety coefficient for the radiuses.
|
Vec3d p = ring.get(i, src, r + sd);
|
||||||
double rcos = (sd + r) * cosphi;
|
|
||||||
double rsin = (sd + r) * sinphi;
|
auto hr = m_mesh.query_ray_hit(p + sd * dir, dir);
|
||||||
|
|
||||||
// Point on the circle on the pin sphere
|
if(ins_check && hr.is_inside()) {
|
||||||
Vec3d p (s(X) + rcos * a(X) + rsin * b(X),
|
if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
|
||||||
s(Y) + rcos * a(Y) + rsin * b(Y),
|
else {
|
||||||
s(Z) + rcos * a(Z) + rsin * b(Z));
|
// re-cast the ray from the outside of the object
|
||||||
|
hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
|
||||||
auto hr = m.query_ray_hit(p + sd*dir, dir);
|
}
|
||||||
|
} else hit = hr;
|
||||||
if(ins_check && hr.is_inside()) {
|
});
|
||||||
if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0);
|
|
||||||
else {
|
|
||||||
// re-cast the ray from the outside of the object
|
|
||||||
auto hr2 =
|
|
||||||
m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
|
|
||||||
|
|
||||||
hits[i] = hr2;
|
|
||||||
}
|
|
||||||
} else hits[i] = hr;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto mit = std::min_element(hits.begin(), hits.end());
|
return min_hit(hits);
|
||||||
|
|
||||||
return *mit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
||||||
@ -419,7 +411,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
|||||||
|
|
||||||
// TODO: This is a workaround to not have a faulty last bridge
|
// TODO: This is a workaround to not have a faulty last bridge
|
||||||
while(ej(Z) >= eupper(Z) /*endz*/) {
|
while(ej(Z) >= eupper(Z) /*endz*/) {
|
||||||
if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
|
if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
|
||||||
{
|
{
|
||||||
m_builder.add_crossbridge(sj, ej, pillar.r);
|
m_builder.add_crossbridge(sj, ej, pillar.r);
|
||||||
was_connected = true;
|
was_connected = true;
|
||||||
@ -430,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
|||||||
Vec3d sjback(ej(X), ej(Y), sj(Z));
|
Vec3d sjback(ej(X), ej(Y), sj(Z));
|
||||||
Vec3d ejback(sj(X), sj(Y), ej(Z));
|
Vec3d ejback(sj(X), sj(Y), ej(Z));
|
||||||
if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
|
if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
|
||||||
bridge_mesh_intersect(sjback, dirv(sjback, ejback),
|
bridge_mesh_distance(sjback, dirv(sjback, ejback),
|
||||||
pillar.r) >= bridge_distance) {
|
pillar.r) >= bridge_distance) {
|
||||||
// need to check collision for the cross stick
|
// need to check collision for the cross stick
|
||||||
m_builder.add_crossbridge(sjback, ejback, pillar.r);
|
m_builder.add_crossbridge(sjback, ejback, pillar.r);
|
||||||
@ -487,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||||||
bridgestart(Z) -= zdiff;
|
bridgestart(Z) -= zdiff;
|
||||||
touchjp(Z) = Zdown;
|
touchjp(Z) = Zdown;
|
||||||
|
|
||||||
double t = bridge_mesh_intersect(headjp, {0,0,-1}, r);
|
double t = bridge_mesh_distance(headjp, DOWN, r);
|
||||||
|
|
||||||
// We can't insert a pillar under the source head to connect
|
// We can't insert a pillar under the source head to connect
|
||||||
// with the nearby pillar's starting junction
|
// with the nearby pillar's starting junction
|
||||||
@ -505,8 +497,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||||||
double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
|
double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
|
||||||
if(bridgeend(Z) < minz) return false;
|
if(bridgeend(Z) < minz) return false;
|
||||||
|
|
||||||
double t = bridge_mesh_intersect(bridgestart,
|
double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
|
||||||
dirv(bridgestart, bridgeend), r);
|
|
||||||
|
|
||||||
// Cannot insert the bridge. (further search might not worth the hassle)
|
// Cannot insert the bridge. (further search might not worth the hassle)
|
||||||
if(t < distance(bridgestart, bridgeend)) return false;
|
if(t < distance(bridgestart, bridgeend)) return false;
|
||||||
@ -633,7 +624,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||||||
};
|
};
|
||||||
|
|
||||||
// We have to check if the bridge is feasible.
|
// We have to check if the bridge is feasible.
|
||||||
if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm())
|
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm())
|
||||||
abort_in_shame();
|
abort_in_shame();
|
||||||
else {
|
else {
|
||||||
// If the new endpoint is below ground, do not make a pillar
|
// If the new endpoint is below ground, do not make a pillar
|
||||||
@ -764,7 +755,7 @@ void SupportTreeBuildsteps::filter()
|
|||||||
{
|
{
|
||||||
auto dir = spheric_to_dir(plr, azm).normalized();
|
auto dir = spheric_to_dir(plr, azm).normalized();
|
||||||
|
|
||||||
double score = pinhead_mesh_intersect(
|
double score = pinhead_mesh_distance(
|
||||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@ -950,11 +941,11 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
|||||||
{
|
{
|
||||||
auto hjp = head.junction_point();
|
auto hjp = head.junction_point();
|
||||||
double r = head.r_back_mm;
|
double r = head.r_back_mm;
|
||||||
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
|
double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
|
||||||
double d = 0, tdown = 0;
|
double d = 0, tdown = 0;
|
||||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||||
|
|
||||||
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
|
while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
|
||||||
d += r;
|
d += r;
|
||||||
|
|
||||||
if(!std::isinf(tdown)) return false;
|
if(!std::isinf(tdown)) return false;
|
||||||
@ -989,7 +980,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
|||||||
auto oresult = solver.optimize_max(
|
auto oresult = solver.optimize_max(
|
||||||
[this, hjp, r_back](double plr, double azm) {
|
[this, hjp, r_back](double plr, double azm) {
|
||||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||||
return bridge_mesh_intersect(hjp, n, r_back);
|
return bridge_mesh_distance(hjp, n, r_back);
|
||||||
},
|
},
|
||||||
initvals(polar, azimuth), // let's start with what we have
|
initvals(polar, azimuth), // let's start with what we have
|
||||||
bound(3*PI/4, PI), // Must not exceed the slope limit
|
bound(3*PI/4, PI), // Must not exceed the slope limit
|
||||||
@ -1259,9 +1250,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||||||
m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
|
m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
|
||||||
|
|
||||||
m_builder.add_junction(s, pillar().r);
|
m_builder.add_junction(s, pillar().r);
|
||||||
double t = bridge_mesh_intersect(pillarsp,
|
double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
|
||||||
dirv(pillarsp, s),
|
pillar().r);
|
||||||
pillar().r);
|
|
||||||
if (distance(pillarsp, s) < t)
|
if (distance(pillarsp, s) < t)
|
||||||
m_builder.add_bridge(pillarsp, s, pillar().r);
|
m_builder.add_bridge(pillarsp, s, pillar().r);
|
||||||
|
|
||||||
@ -1312,8 +1302,8 @@ void SupportTreeBuildsteps::routing_headless()
|
|||||||
Vec3d sj = sp + R * n; // stick start point
|
Vec3d sj = sp + R * n; // stick start point
|
||||||
|
|
||||||
// This is only for checking
|
// This is only for checking
|
||||||
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
|
double idist = bridge_mesh_distance(sph, DOWN, R, true);
|
||||||
double realdist = ray_mesh_intersect(sj, DOWN);
|
double realdist = ray_mesh_intersect(sj, DOWN).distance();
|
||||||
double dist = realdist;
|
double dist = realdist;
|
||||||
|
|
||||||
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
||||||
|
@ -206,10 +206,10 @@ class SupportTreeBuildsteps {
|
|||||||
// When bridging heads to pillars... TODO: find a cleaner solution
|
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||||
ccr::BlockingMutex m_bridge_mutex;
|
ccr::BlockingMutex m_bridge_mutex;
|
||||||
|
|
||||||
inline double ray_mesh_intersect(const Vec3d& s,
|
inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,
|
||||||
const Vec3d& dir)
|
const Vec3d& dir)
|
||||||
{
|
{
|
||||||
return m_mesh.query_ray_hit(s, dir).distance();
|
return m_mesh.query_ray_hit(s, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will test if a future pinhead would not collide with the
|
// This function will test if a future pinhead would not collide with the
|
||||||
@ -229,6 +229,11 @@ class SupportTreeBuildsteps {
|
|||||||
double r_pin,
|
double r_pin,
|
||||||
double r_back,
|
double r_back,
|
||||||
double width);
|
double width);
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
inline double pinhead_mesh_distance(Args&&...args) {
|
||||||
|
return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
|
||||||
|
}
|
||||||
|
|
||||||
// Checking bridge (pillar and stick as well) intersection with the model.
|
// Checking bridge (pillar and stick as well) intersection with the model.
|
||||||
// If the function is used for headless sticks, the ins_check parameter
|
// If the function is used for headless sticks, the ins_check parameter
|
||||||
@ -243,6 +248,11 @@ class SupportTreeBuildsteps {
|
|||||||
const Vec3d& dir,
|
const Vec3d& dir,
|
||||||
double r,
|
double r,
|
||||||
bool ins_check = false);
|
bool ins_check = false);
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
inline double bridge_mesh_distance(Args&&...args) {
|
||||||
|
return bridge_mesh_intersect(std::forward<Args>(args)...).distance();
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function for interconnecting two pillars with zig-zag bridges.
|
// Helper function for interconnecting two pillars with zig-zag bridges.
|
||||||
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
||||||
|
@ -1095,8 +1095,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const
|
|||||||
const std::vector<ExPolygons>& v = o == soModel? m_po->get_model_slices() :
|
const std::vector<ExPolygons>& v = o == soModel? m_po->get_model_slices() :
|
||||||
m_po->get_support_slices();
|
m_po->get_support_slices();
|
||||||
|
|
||||||
if(idx >= v.size()) return EMPTY_SLICE;
|
|
||||||
|
|
||||||
return idx >= v.size() ? EMPTY_SLICE : v[idx];
|
return idx >= v.size() ? EMPTY_SLICE : v[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,9 +138,9 @@ public:
|
|||||||
// Returns the current layer height
|
// Returns the current layer height
|
||||||
float layer_height() const { return m_height; }
|
float layer_height() const { return m_height; }
|
||||||
|
|
||||||
bool is_valid() const { return ! std::isnan(m_slice_z); }
|
bool is_valid() const { return m_po && ! std::isnan(m_slice_z); }
|
||||||
|
|
||||||
const SLAPrintObject* print_obj() const { assert(m_po); return m_po; }
|
const SLAPrintObject* print_obj() const { return m_po; }
|
||||||
|
|
||||||
// Methods for setting the indices into the slice vectors.
|
// Methods for setting the indices into the slice vectors.
|
||||||
void set_model_slice_idx(const SLAPrintObject &po, size_t id) {
|
void set_model_slice_idx(const SLAPrintObject &po, size_t id) {
|
||||||
|
@ -465,14 +465,16 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get polygons for all instances in the object
|
// get polygons for all instances in the object
|
||||||
static ClipperPolygons get_all_polygons(
|
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
|
||||||
const ExPolygons & input_polygons,
|
|
||||||
const std::vector<SLAPrintObject::Instance> &instances,
|
|
||||||
bool is_lefthanded)
|
|
||||||
{
|
{
|
||||||
namespace sl = libnest2d::sl;
|
namespace sl = libnest2d::sl;
|
||||||
|
|
||||||
|
if (!record.print_obj()) return {};
|
||||||
|
|
||||||
ClipperPolygons polygons;
|
ClipperPolygons polygons;
|
||||||
|
auto &input_polygons = record.get_slice(o);
|
||||||
|
auto &instances = record.print_obj()->instances();
|
||||||
|
bool is_lefthanded = record.print_obj()->is_left_handed();
|
||||||
polygons.reserve(input_polygons.size() * instances.size());
|
polygons.reserve(input_polygons.size() * instances.size());
|
||||||
|
|
||||||
for (const ExPolygon& polygon : input_polygons) {
|
for (const ExPolygon& polygon : input_polygons) {
|
||||||
@ -545,6 +547,12 @@ void SLAPrint::Steps::initialize_printer_input()
|
|||||||
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
||||||
|
|
||||||
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||||
|
if (!slicerecord.is_valid())
|
||||||
|
throw std::runtime_error(
|
||||||
|
L("There are unprintable objects. Try to "
|
||||||
|
"adjust support settings to make the "
|
||||||
|
"objects printable."));
|
||||||
|
|
||||||
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
||||||
|
|
||||||
// Neat trick to round the layer levels to the grid.
|
// Neat trick to round the layer levels to the grid.
|
||||||
@ -647,22 +655,13 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
|||||||
supports_polygons.reserve(c);
|
supports_polygons.reserve(c);
|
||||||
|
|
||||||
for(const SliceRecord& record : layer.slices()) {
|
for(const SliceRecord& record : layer.slices()) {
|
||||||
const SLAPrintObject *po = record.print_obj();
|
|
||||||
|
|
||||||
const ExPolygons &modelslices = record.get_slice(soModel);
|
ClipperPolygons modelslices = get_all_polygons(record, soModel);
|
||||||
|
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
|
||||||
bool is_lefth = record.print_obj()->is_left_handed();
|
|
||||||
if (!modelslices.empty()) {
|
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
|
||||||
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
|
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
|
||||||
for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExPolygons &supportslices = record.get_slice(soSupport);
|
|
||||||
|
|
||||||
if (!supportslices.empty()) {
|
|
||||||
ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
|
|
||||||
for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model_polygons = polyunion(model_polygons);
|
model_polygons = polyunion(model_polygons);
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -63,7 +64,7 @@ namespace implementation {
|
|||||||
template<bool B, class T>
|
template<bool B, class T>
|
||||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||||
|
|
||||||
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
|
// Meta predicates for floating, integer and generic arithmetic types
|
||||||
template<class T, class O = T>
|
template<class T, class O = T>
|
||||||
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
|
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user