Bugfixes and new tests for pillar search
This commit is contained in:
parent
a20659fc2d
commit
0bbd50eaa0
@ -21,7 +21,7 @@ class Properties
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
constexpr bool group_pillars() const noexcept { return true; }
|
constexpr bool group_pillars() const noexcept { return false; }
|
||||||
|
|
||||||
// Maximum slope for bridges of the tree
|
// Maximum slope for bridges of the tree
|
||||||
Properties &max_slope(double val) noexcept
|
Properties &max_slope(double val) noexcept
|
||||||
|
@ -26,30 +26,6 @@ using Slic3r::opt::AlgNLoptGenetic;
|
|||||||
using Slic3r::Geometry::dir_to_spheric;
|
using Slic3r::Geometry::dir_to_spheric;
|
||||||
using Slic3r::Geometry::spheric_to_dir;
|
using Slic3r::Geometry::spheric_to_dir;
|
||||||
|
|
||||||
// Helper function for pillar interconnection where pairs of already connected
|
|
||||||
// pillars should be checked for not to be processed again. This can be done
|
|
||||||
// in constant time with a set of hash values uniquely representing a pair of
|
|
||||||
// integers. The order of numbers within the pair should not matter, it has
|
|
||||||
// the same unique hash. The hash value has to have twice as many bits as the
|
|
||||||
// arguments need. If the same integral type is used for args and return val,
|
|
||||||
// make sure the arguments use only the half of the type's bit depth.
|
|
||||||
template<class I, class DoubleI = IntegerOnly<I>>
|
|
||||||
IntegerOnly<DoubleI> pairhash(I a, I b)
|
|
||||||
{
|
|
||||||
using std::ceil; using std::log2; using std::max; using std::min;
|
|
||||||
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
|
||||||
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
|
||||||
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
|
||||||
|
|
||||||
I g = min(a, b), l = max(a, b);
|
|
||||||
|
|
||||||
// Assume the hash will fit into the output variable
|
|
||||||
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
|
||||||
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
|
||||||
|
|
||||||
return (DoubleI(g) << shift) + l;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give points on a 3D ring with given center, radius and orientation
|
// Give points on a 3D ring with given center, radius and orientation
|
||||||
// method based on:
|
// method based on:
|
||||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||||
@ -294,62 +270,6 @@ Hit pinhead_mesh_hit(Ex ex,
|
|||||||
head.r_back_mm, head.width_mm, safety_d);
|
head.r_back_mm, head.width_mm, safety_d);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Ex>
|
|
||||||
std::optional<DiffBridge> search_widening_path(Ex policy,
|
|
||||||
const SupportableMesh &sm,
|
|
||||||
const Vec3d &jp,
|
|
||||||
const Vec3d &dir,
|
|
||||||
double radius,
|
|
||||||
double new_radius)
|
|
||||||
{
|
|
||||||
double w = radius + 2 * sm.cfg.head_back_radius_mm;
|
|
||||||
double stopval = w + jp.z() - ground_level(sm);
|
|
||||||
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
|
|
||||||
|
|
||||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
|
||||||
|
|
||||||
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
|
|
||||||
|
|
||||||
auto oresult = solver.to_max().optimize(
|
|
||||||
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
|
|
||||||
auto &[plr, azm, t] = input;
|
|
||||||
|
|
||||||
auto d = spheric_to_dir(plr, azm).normalized();
|
|
||||||
|
|
||||||
auto sd = sm.cfg.safety_distance(new_radius);
|
|
||||||
|
|
||||||
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
|
||||||
new_radius, t, sd)
|
|
||||||
.distance();
|
|
||||||
|
|
||||||
Beam beam{jp + t * d, d, new_radius};
|
|
||||||
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
|
||||||
|
|
||||||
if (ret > t && std::isinf(down))
|
|
||||||
ret += jp.z() - ground_level(sm);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
initvals({polar, azimuth, w}), // start with what we have
|
|
||||||
bounds({
|
|
||||||
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
|
|
||||||
{-PI, PI}, // azimuth can be a full search
|
|
||||||
{radius + sm.cfg.head_back_radius_mm,
|
|
||||||
fallback_ratio * sm.cfg.max_bridge_length_mm}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (oresult.score >= stopval) {
|
|
||||||
polar = std::get<0>(oresult.optimum);
|
|
||||||
azimuth = std::get<1>(oresult.optimum);
|
|
||||||
double t = std::get<2>(oresult.optimum);
|
|
||||||
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
|
|
||||||
|
|
||||||
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline double distance(const SupportPoint &a, const SupportPoint &b)
|
inline double distance(const SupportPoint &a, const SupportPoint &b)
|
||||||
{
|
{
|
||||||
return (a.pos - b.pos).norm();
|
return (a.pos - b.pos).norm();
|
||||||
@ -518,167 +438,6 @@ struct GroundConnection {
|
|||||||
operator bool() const { return pillar_base.has_value() && !path.empty(); }
|
operator bool() const { return pillar_base.has_value() && !path.empty(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Ex>
|
|
||||||
GroundConnection find_pillar_route(Ex policy,
|
|
||||||
const SupportableMesh &sm,
|
|
||||||
const Junction &source,
|
|
||||||
const Vec3d &sourcedir,
|
|
||||||
double end_radius)
|
|
||||||
{
|
|
||||||
GroundConnection ret;
|
|
||||||
ret.path.emplace_back(source);
|
|
||||||
|
|
||||||
double sd = sm.cfg.safety_distance(source.r);
|
|
||||||
auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)};
|
|
||||||
|
|
||||||
auto hit = beam_mesh_hit(policy,
|
|
||||||
sm.emesh,
|
|
||||||
Beam{{source.pos, source.r}, {gp, end_radius}},
|
|
||||||
sd);
|
|
||||||
|
|
||||||
if (std::isinf(hit.distance())) {
|
|
||||||
double base_radius = std::max(sm.cfg.base_radius_mm, end_radius);
|
|
||||||
|
|
||||||
ret.pillar_base =
|
|
||||||
Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
//template<class Ex>
|
|
||||||
//GroundConnection find_pillar_route(Ex policy,
|
|
||||||
// const SupportableMesh &sm,
|
|
||||||
// const Junction &source,
|
|
||||||
// const Vec3d &sourcedir,
|
|
||||||
// double end_radius)
|
|
||||||
//{
|
|
||||||
// GroundConnection ret;
|
|
||||||
|
|
||||||
// ret.path.emplace_back(source);
|
|
||||||
|
|
||||||
// Vec3d jp = source.pos, endp = jp, dir = sourcedir;
|
|
||||||
// bool can_add_base = false/*, non_head = false*/;
|
|
||||||
|
|
||||||
// double gndlvl = 0.; // The Z level where pedestals should be
|
|
||||||
// double jp_gnd = 0.; // The lowest Z where a junction center can be
|
|
||||||
// double gap_dist = 0.; // The gap distance between the model and the pad
|
|
||||||
// double radius = source.r;
|
|
||||||
// double sd = sm.cfg.safety_distance(radius);
|
|
||||||
|
|
||||||
// double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
|
||||||
|
|
||||||
// auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
|
|
||||||
|
|
||||||
// auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd, end_radius]
|
|
||||||
// (bool base_en = true)
|
|
||||||
// {
|
|
||||||
// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
|
|
||||||
// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
|
||||||
// gndlvl = ground_level(sm);
|
|
||||||
// if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
|
|
||||||
// jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
|
|
||||||
// gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// eval_limits();
|
|
||||||
|
|
||||||
// // We are dealing with a mini pillar that's potentially too long
|
|
||||||
// if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
|
|
||||||
// {
|
|
||||||
// std::optional<DiffBridge> diffbr =
|
|
||||||
// search_widening_path(policy, sm, jp, dir, radius,
|
|
||||||
// sm.cfg.head_back_radius_mm);
|
|
||||||
|
|
||||||
// if (diffbr && diffbr->endp.z() > jp_gnd) {
|
|
||||||
// endp = diffbr->endp;
|
|
||||||
// radius = diffbr->end_r;
|
|
||||||
// ret.path.emplace_back(endp, radius);
|
|
||||||
// dir = diffbr->get_dir();
|
|
||||||
// eval_limits();
|
|
||||||
// } else return ret;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (sm.cfg.object_elevation_mm < EPSILON)
|
|
||||||
// {
|
|
||||||
// // get a suitable direction for the corrector bridge. It is the
|
|
||||||
// // original sourcedir's azimuth but the polar angle is saturated to the
|
|
||||||
// // configured bridge slope.
|
|
||||||
// auto [polar, azimuth] = dir_to_spheric(dir);
|
|
||||||
// polar = PI - sm.cfg.bridge_slope;
|
|
||||||
// Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
|
||||||
// double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
|
|
||||||
// double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
|
||||||
// t = 0.;
|
|
||||||
|
|
||||||
// double zd = endp.z() - jp_gnd;
|
|
||||||
// double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
|
||||||
// tmax = std::min(tmax, tmax2);
|
|
||||||
|
|
||||||
// Vec3d nexp = endp;
|
|
||||||
// double dlast = 0.;
|
|
||||||
// double rnext = radius;
|
|
||||||
// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
|
||||||
// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) &&
|
|
||||||
// t < tmax)
|
|
||||||
// {
|
|
||||||
// t += radius;
|
|
||||||
// nexp = endp + t * d;
|
|
||||||
// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm)));
|
|
||||||
// rnext = rnext + bridge_ratio * (end_radius - rnext);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // If could not find avoidance bridge for the pad gap, try again
|
|
||||||
// // without the pillar base
|
|
||||||
// if (dlast < gap_dist && can_add_base) {
|
|
||||||
// nexp = endp;
|
|
||||||
// t = 0.;
|
|
||||||
// rnext = radius;
|
|
||||||
// can_add_base = false;
|
|
||||||
// eval_limits(can_add_base);
|
|
||||||
|
|
||||||
// zd = endp.z() - jp_gnd;
|
|
||||||
// tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
|
||||||
// tmax = std::min(tmax, tmax2);
|
|
||||||
|
|
||||||
// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
|
||||||
// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && t < tmax) {
|
|
||||||
// t += radius;
|
|
||||||
// nexp = endp + t * d;
|
|
||||||
|
|
||||||
// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm)));
|
|
||||||
// rnext = rnext + bridge_ratio * (end_radius - rnext);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Could not find a path to avoid the pad gap
|
|
||||||
// if (dlast < gap_dist) {
|
|
||||||
// ret.path.clear();
|
|
||||||
// return ret;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (t > 0.) { // Need to make additional bridge
|
|
||||||
// ret.path.emplace_back(nexp, rnext);
|
|
||||||
// endp = nexp;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Vec3d gp = to_floor(endp);
|
|
||||||
// auto hit = beam_mesh_hit(policy, sm.emesh,
|
|
||||||
// Beam{{endp, radius}, {gp, end_radius}}, sd);
|
|
||||||
|
|
||||||
// if (std::isinf(hit.distance())) {
|
|
||||||
// double base_radius = can_add_base ?
|
|
||||||
// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
|
||||||
|
|
||||||
// Vec3d gp = to_floor(endp);
|
|
||||||
// ret.pillar_base =
|
|
||||||
// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return ret;
|
|
||||||
//}
|
|
||||||
|
|
||||||
inline long build_ground_connection(SupportTreeBuilder &builder,
|
inline long build_ground_connection(SupportTreeBuilder &builder,
|
||||||
const SupportableMesh &sm,
|
const SupportableMesh &sm,
|
||||||
const GroundConnection &conn)
|
const GroundConnection &conn)
|
||||||
@ -714,89 +473,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Ex>
|
|
||||||
GroundConnection find_ground_connection(
|
|
||||||
Ex policy,
|
|
||||||
const SupportableMesh &sm,
|
|
||||||
const Junction &j,
|
|
||||||
const Vec3d &dir,
|
|
||||||
double end_r)
|
|
||||||
{
|
|
||||||
auto hjp = j.pos;
|
|
||||||
double r = j.r;
|
|
||||||
auto sd = sm.cfg.safety_distance(r);
|
|
||||||
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
|
||||||
|
|
||||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
|
||||||
t = std::min(t, sm.cfg.max_bridge_length_mm);
|
|
||||||
double d = 0.;
|
|
||||||
|
|
||||||
GroundConnection gnd_route;
|
|
||||||
|
|
||||||
while (!gnd_route && d < t) {
|
|
||||||
Vec3d endp = hjp + d * dir;
|
|
||||||
double bridge_ratio = d / (d + (endp.z() - ground_level(sm)));
|
|
||||||
double pill_r = r + bridge_ratio * (end_r - r);
|
|
||||||
gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
|
|
||||||
|
|
||||||
d += r;
|
|
||||||
}
|
|
||||||
|
|
||||||
GroundConnection ret;
|
|
||||||
|
|
||||||
if (d > 0.)
|
|
||||||
ret.path.emplace_back(j);
|
|
||||||
|
|
||||||
for (auto &p : gnd_route.path)
|
|
||||||
ret.path.emplace_back(p);
|
|
||||||
|
|
||||||
// This will ultimately determine if the route is valid or not
|
|
||||||
// but the path junctions will be provided anyways, so invalid paths
|
|
||||||
// can be inspected
|
|
||||||
ret.pillar_base = gnd_route.pillar_base;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Ex>
|
|
||||||
GroundConnection optimize_ground_connection(
|
|
||||||
Ex policy,
|
|
||||||
const SupportableMesh &sm,
|
|
||||||
const Junction &j,
|
|
||||||
double end_radius,
|
|
||||||
const Vec3d &init_dir = DOWN)
|
|
||||||
{
|
|
||||||
double downdst = j.pos.z() - ground_level(sm);
|
|
||||||
|
|
||||||
auto res = find_ground_connection(policy, sm, j, init_dir, end_radius);
|
|
||||||
if (res)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
// Optimize bridge direction:
|
|
||||||
// Straight path failed so we will try to search for a suitable
|
|
||||||
// direction out of the cavity.
|
|
||||||
auto [polar, azimuth] = dir_to_spheric(init_dir);
|
|
||||||
|
|
||||||
Optimizer<opt::AlgNLoptMLSL> solver(get_criteria(sm.cfg).stop_score(1e6));
|
|
||||||
solver.seed(0); // we want deterministic behavior
|
|
||||||
|
|
||||||
auto sd = sm.cfg.safety_distance(j.r);
|
|
||||||
auto oresult = solver.to_max().optimize(
|
|
||||||
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
|
|
||||||
auto &[plr, azm] = input;
|
|
||||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
|
||||||
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
|
|
||||||
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
|
||||||
},
|
|
||||||
initvals({polar, azimuth}), // let's start with what we have
|
|
||||||
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
|
|
||||||
);
|
|
||||||
|
|
||||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
|
||||||
|
|
||||||
return find_ground_connection(policy, sm, j, bridgedir, end_radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Ex>
|
template<class Ex>
|
||||||
GroundConnection deepsearch_ground_connection(
|
GroundConnection deepsearch_ground_connection(
|
||||||
Ex policy,
|
Ex policy,
|
||||||
@ -861,10 +537,10 @@ GroundConnection deepsearch_ground_connection(
|
|||||||
if (gap < zelev_gap)
|
if (gap < zelev_gap)
|
||||||
ret = full_len - zelev_gap + gap;
|
ret = full_len - zelev_gap + gap;
|
||||||
else // success
|
else // success
|
||||||
ret = StopScore;
|
ret = StopScore + EPSILON;
|
||||||
} else {
|
} else {
|
||||||
// No zero elevation, return success
|
// No zero elevation, return success
|
||||||
ret = StopScore;
|
ret = StopScore + EPSILON;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ground route is not free
|
// Ground route is not free
|
||||||
@ -902,6 +578,7 @@ GroundConnection deepsearch_ground_connection(
|
|||||||
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
|
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
|
||||||
|
|
||||||
conn.path.emplace_back(j);
|
conn.path.emplace_back(j);
|
||||||
|
if (bridge_len > EPSILON)
|
||||||
conn.path.emplace_back(Junction{bridge_end, bridge_r});
|
conn.path.emplace_back(Junction{bridge_end, bridge_r});
|
||||||
|
|
||||||
conn.pillar_base =
|
conn.pillar_base =
|
||||||
|
@ -8,6 +8,86 @@
|
|||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
// Helper function for pillar interconnection where pairs of already connected
|
||||||
|
// pillars should be checked for not to be processed again. This can be done
|
||||||
|
// in constant time with a set of hash values uniquely representing a pair of
|
||||||
|
// integers. The order of numbers within the pair should not matter, it has
|
||||||
|
// the same unique hash. The hash value has to have twice as many bits as the
|
||||||
|
// arguments need. If the same integral type is used for args and return val,
|
||||||
|
// make sure the arguments use only the half of the type's bit depth.
|
||||||
|
template<class I, class DoubleI = IntegerOnly<I>>
|
||||||
|
IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||||
|
{
|
||||||
|
using std::ceil; using std::log2; using std::max; using std::min;
|
||||||
|
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
||||||
|
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
||||||
|
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||||
|
|
||||||
|
I g = min(a, b), l = max(a, b);
|
||||||
|
|
||||||
|
// Assume the hash will fit into the output variable
|
||||||
|
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
||||||
|
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
||||||
|
|
||||||
|
return (DoubleI(g) << shift) + l;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Ex>
|
||||||
|
std::optional<DiffBridge> search_widening_path(Ex policy,
|
||||||
|
const SupportableMesh &sm,
|
||||||
|
const Vec3d &jp,
|
||||||
|
const Vec3d &dir,
|
||||||
|
double radius,
|
||||||
|
double new_radius)
|
||||||
|
{
|
||||||
|
double w = radius + 2 * sm.cfg.head_back_radius_mm;
|
||||||
|
double stopval = w + jp.z() - ground_level(sm);
|
||||||
|
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
|
||||||
|
|
||||||
|
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||||
|
|
||||||
|
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
|
||||||
|
|
||||||
|
auto oresult = solver.to_max().optimize(
|
||||||
|
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
|
||||||
|
auto &[plr, azm, t] = input;
|
||||||
|
|
||||||
|
auto d = spheric_to_dir(plr, azm).normalized();
|
||||||
|
|
||||||
|
auto sd = sm.cfg.safety_distance(new_radius);
|
||||||
|
|
||||||
|
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
||||||
|
new_radius, t, sd)
|
||||||
|
.distance();
|
||||||
|
|
||||||
|
Beam beam{jp + t * d, d, new_radius};
|
||||||
|
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
||||||
|
|
||||||
|
if (ret > t && std::isinf(down))
|
||||||
|
ret += jp.z() - ground_level(sm);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
initvals({polar, azimuth, w}), // start with what we have
|
||||||
|
bounds({
|
||||||
|
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
|
||||||
|
{-PI, PI}, // azimuth can be a full search
|
||||||
|
{radius + sm.cfg.head_back_radius_mm,
|
||||||
|
fallback_ratio * sm.cfg.max_bridge_length_mm}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (oresult.score >= stopval) {
|
||||||
|
polar = std::get<0>(oresult.optimum);
|
||||||
|
azimuth = std::get<1>(oresult.optimum);
|
||||||
|
double t = std::get<2>(oresult.optimum);
|
||||||
|
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
|
||||||
|
|
||||||
|
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// This is a proxy function for pillar creation which will mind the gap
|
// This is a proxy function for pillar creation which will mind the gap
|
||||||
// between the pad and the model bottom in zero elevation mode.
|
// between the pad and the model bottom in zero elevation mode.
|
||||||
// 'pinhead_junctionpt' is the starting junction point which needs to be
|
// 'pinhead_junctionpt' is the starting junction point which needs to be
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#include <unordered_set>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
|
|
||||||
test_pairhash<int, int>();
|
|
||||||
test_pairhash<int, long>();
|
|
||||||
test_pairhash<unsigned, unsigned>();
|
|
||||||
test_pairhash<unsigned, unsigned long>();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Support point generator should be deterministic if seeded",
|
TEST_CASE("Support point generator should be deterministic if seeded",
|
||||||
"[SLASupportGeneration], [SLAPointGen]") {
|
"[SLASupportGeneration], [SLAPointGen]") {
|
||||||
TriangleMesh mesh = load_model("A_upsidedown.obj");
|
TriangleMesh mesh = load_model("A_upsidedown.obj");
|
||||||
|
@ -1,8 +1,158 @@
|
|||||||
#include <catch2/catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <test_utils.hpp>
|
#include <test_utils.hpp>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||||
|
#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp"
|
||||||
|
|
||||||
|
// Test pair hash for 'nums' random number pairs.
|
||||||
|
template <class I, class II> void test_pairhash()
|
||||||
|
{
|
||||||
|
const constexpr size_t nums = 1000;
|
||||||
|
I A[nums] = {0}, B[nums] = {0};
|
||||||
|
std::unordered_set<I> CH;
|
||||||
|
std::unordered_map<II, std::pair<I, I>> ints;
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
|
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||||
|
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||||
|
|
||||||
|
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||||
|
if (std::is_signed<I>::value) bits -= 1;
|
||||||
|
const I Imin = 0;
|
||||||
|
const I Imax = I(std::pow(2., bits) - 1);
|
||||||
|
|
||||||
|
std::uniform_int_distribution<I> dis(Imin, Imax);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nums;) {
|
||||||
|
I a = dis(gen);
|
||||||
|
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nums;) {
|
||||||
|
I b = dis(gen);
|
||||||
|
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nums; ++i) {
|
||||||
|
I a = A[i], b = B[i];
|
||||||
|
|
||||||
|
REQUIRE(a != b);
|
||||||
|
|
||||||
|
II hash_ab = Slic3r::sla::pairhash<I, II>(a, b);
|
||||||
|
II hash_ba = Slic3r::sla::pairhash<I, II>(b, a);
|
||||||
|
REQUIRE(hash_ab == hash_ba);
|
||||||
|
|
||||||
|
auto it = ints.find(hash_ab);
|
||||||
|
|
||||||
|
if (it != ints.end()) {
|
||||||
|
REQUIRE((
|
||||||
|
(it->second.first == a && it->second.second == b) ||
|
||||||
|
(it->second.first == b && it->second.second == a)
|
||||||
|
));
|
||||||
|
} else
|
||||||
|
ints[hash_ab] = std::make_pair(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") {
|
||||||
|
test_pairhash<int, int>();
|
||||||
|
test_pairhash<int, long>();
|
||||||
|
test_pairhash<unsigned, unsigned>();
|
||||||
|
test_pairhash<unsigned, unsigned long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn,
|
||||||
|
const Slic3r::sla::SupportableMesh &sm,
|
||||||
|
const Slic3r::sla::Junction &j,
|
||||||
|
double end_r,
|
||||||
|
const std::string &stl_fname = "output.stl")
|
||||||
|
{
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
sla::SupportTreeBuilder builder;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
builder.add_junction(j);
|
||||||
|
|
||||||
|
sla::build_ground_connection(builder, sm, conn);
|
||||||
|
|
||||||
|
indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh();
|
||||||
|
its_merge(mesh, builder.merged_mesh());
|
||||||
|
|
||||||
|
its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
REQUIRE(bool(conn));
|
||||||
|
|
||||||
|
// The route should include the source and one avoidance junction.
|
||||||
|
REQUIRE(conn.path.size() == 2);
|
||||||
|
|
||||||
|
// Check if the radius increases with each node
|
||||||
|
REQUIRE(conn.path.front().r < conn.path.back().r);
|
||||||
|
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
||||||
|
|
||||||
|
// The end radius and the pillar base's upper radius should match
|
||||||
|
REQUIRE(conn.pillar_base->r_top == Approx(end_r));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Pillar search dumb case", "[suptreeutils]") {
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
constexpr double FromR = 0.5;
|
||||||
|
auto j = sla::Junction{Vec3d::Zero(), FromR};
|
||||||
|
|
||||||
|
SECTION("with empty mesh") {
|
||||||
|
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||||
|
sla::SupportPoints{},
|
||||||
|
sla::SupportTreeConfig{}};
|
||||||
|
|
||||||
|
constexpr double EndR = 1.;
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
|
||||||
|
|
||||||
|
REQUIRE(conn);
|
||||||
|
REQUIRE(conn.path.size() == 1);
|
||||||
|
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("with zero R source and destination") {
|
||||||
|
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||||
|
sla::SupportPoints{},
|
||||||
|
sla::SupportTreeConfig{}};
|
||||||
|
|
||||||
|
j.r = 0.;
|
||||||
|
constexpr double EndR = 0.;
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
|
||||||
|
|
||||||
|
REQUIRE(conn);
|
||||||
|
REQUIRE(conn.path.size() == 1);
|
||||||
|
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||||
|
REQUIRE(conn.pillar_base->r_top == Approx(0.));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("with zero init direction") {
|
||||||
|
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||||
|
sla::SupportPoints{},
|
||||||
|
sla::SupportTreeConfig{}};
|
||||||
|
|
||||||
|
constexpr double EndR = 1.;
|
||||||
|
Vec3d init_dir = Vec3d::Zero();
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir);
|
||||||
|
|
||||||
|
REQUIRE(conn);
|
||||||
|
REQUIRE(conn.path.size() == 1);
|
||||||
|
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
||||||
{
|
{
|
||||||
@ -27,34 +177,12 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
|||||||
|
|
||||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||||
|
|
||||||
|
SECTION("without elevation") {
|
||||||
|
|
||||||
sla::GroundConnection conn =
|
sla::GroundConnection conn =
|
||||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
eval_ground_conn(conn, sm, j, EndRadius, "disk.stl");
|
||||||
|
|
||||||
sla::SupportTreeBuilder builder;
|
|
||||||
|
|
||||||
if (!conn)
|
|
||||||
builder.add_junction(j);
|
|
||||||
|
|
||||||
sla::build_ground_connection(builder, sm, conn);
|
|
||||||
|
|
||||||
its_merge(disk, builder.merged_mesh());
|
|
||||||
|
|
||||||
its_write_stl_ascii("output_disk.stl", "disk", disk);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
REQUIRE(bool(conn));
|
|
||||||
|
|
||||||
// The route should include the source and one avoidance junction.
|
|
||||||
REQUIRE(conn.path.size() == 2);
|
|
||||||
|
|
||||||
// Check if the radius increases with each node
|
|
||||||
REQUIRE(conn.path.front().r < conn.path.back().r);
|
|
||||||
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
|
||||||
|
|
||||||
// The end radius and the pillar base's upper radius should match
|
|
||||||
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
|
||||||
|
|
||||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||||
// edge.
|
// edge.
|
||||||
@ -63,58 +191,13 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
|||||||
REQUIRE(pR + FromRadius > CylRadius);
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]")
|
SECTION("with elevation") {
|
||||||
{
|
sm.cfg.object_elevation_mm = 0.;
|
||||||
// In this test there will be a disk mesh with some radius, centered at
|
|
||||||
// (0, 0, 0) and above the disk, a junction from which the support pillar
|
|
||||||
// should be routed. The algorithm needs to find an avoidance route.
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
|
||||||
|
|
||||||
constexpr double FromRadius = .5;
|
|
||||||
constexpr double EndRadius = 1.;
|
|
||||||
constexpr double CylRadius = 4.;
|
|
||||||
constexpr double CylHeight = 1.;
|
|
||||||
|
|
||||||
sla::SupportTreeConfig cfg;
|
|
||||||
cfg.object_elevation_mm = 0.;
|
|
||||||
|
|
||||||
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
|
|
||||||
|
|
||||||
// 2.5 * CyRadius height should be enough to be able to insert a bridge
|
|
||||||
// with 45 degree tilt above the disk.
|
|
||||||
sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius};
|
|
||||||
|
|
||||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
|
||||||
|
|
||||||
sla::GroundConnection conn =
|
sla::GroundConnection conn =
|
||||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl");
|
||||||
|
|
||||||
sla::SupportTreeBuilder builder;
|
|
||||||
|
|
||||||
if (!conn)
|
|
||||||
builder.add_junction(j);
|
|
||||||
|
|
||||||
sla::build_ground_connection(builder, sm, conn);
|
|
||||||
|
|
||||||
its_merge(disk, builder.merged_mesh());
|
|
||||||
|
|
||||||
its_write_stl_ascii("output_disk_ze.stl", "disk", disk);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
REQUIRE(bool(conn));
|
|
||||||
|
|
||||||
// The route should include the source and one avoidance junction.
|
|
||||||
REQUIRE(conn.path.size() == 2);
|
|
||||||
|
|
||||||
// Check if the radius increases with each node
|
|
||||||
REQUIRE(conn.path.front().r < conn.path.back().r);
|
|
||||||
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
|
||||||
|
|
||||||
// The end radius and the pillar base's upper radius should match
|
|
||||||
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
|
||||||
|
|
||||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||||
// edge.
|
// edge.
|
||||||
@ -122,6 +205,7 @@ TEST_CASE("Avoid disk below junction - Zero elevation", "[suptreeutils]")
|
|||||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||||
REQUIRE(pR + FromRadius > CylRadius);
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
|
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
|
||||||
{
|
{
|
||||||
@ -150,34 +234,11 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]"
|
|||||||
|
|
||||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||||
|
|
||||||
|
SECTION("without elevation") {
|
||||||
sla::GroundConnection conn =
|
sla::GroundConnection conn =
|
||||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl");
|
||||||
|
|
||||||
sla::SupportTreeBuilder builder;
|
|
||||||
|
|
||||||
if (!conn)
|
|
||||||
builder.add_junction(j);
|
|
||||||
|
|
||||||
sla::build_ground_connection(builder, sm, conn);
|
|
||||||
|
|
||||||
its_merge(disk, builder.merged_mesh());
|
|
||||||
|
|
||||||
its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
REQUIRE(bool(conn));
|
|
||||||
|
|
||||||
// The route should include the source and one avoidance junction.
|
|
||||||
REQUIRE(conn.path.size() == 2);
|
|
||||||
|
|
||||||
// Check if the radius increases with each node
|
|
||||||
REQUIRE(conn.path.front().r < conn.path.back().r);
|
|
||||||
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
|
||||||
|
|
||||||
// The end radius end the pillar base's upper radius should match
|
|
||||||
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
|
||||||
|
|
||||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||||
// edge.
|
// edge.
|
||||||
@ -185,3 +246,19 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]"
|
|||||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||||
REQUIRE(pR + FromRadius > CylRadius);
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("without elevation") {
|
||||||
|
sm.cfg.object_elevation_mm = 0.;
|
||||||
|
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
|
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl");
|
||||||
|
|
||||||
|
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||||
|
// edge.
|
||||||
|
auto p = conn.path.back().pos;
|
||||||
|
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||||
|
REQUIRE(pR + FromRadius > CylRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,14 +14,12 @@
|
|||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "libslic3r/SLA/Pad.hpp"
|
#include "libslic3r/SLA/Pad.hpp"
|
||||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
|
||||||
#include "libslic3r/SLA/SupportPointGenerator.hpp"
|
#include "libslic3r/SLA/SupportPointGenerator.hpp"
|
||||||
#include "libslic3r/SLA/AGGRaster.hpp"
|
#include "libslic3r/SLA/AGGRaster.hpp"
|
||||||
#include "libslic3r/SLA/ConcaveHull.hpp"
|
#include "libslic3r/SLA/ConcaveHull.hpp"
|
||||||
#include "libslic3r/MTUtils.hpp"
|
#include "libslic3r/MTUtils.hpp"
|
||||||
|
|
||||||
#include "libslic3r/SVG.hpp"
|
#include "libslic3r/SVG.hpp"
|
||||||
#include "libslic3r/Format/OBJ.hpp"
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@ -111,58 +109,6 @@ inline void test_support_model_collision(
|
|||||||
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
|
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test pair hash for 'nums' random number pairs.
|
|
||||||
template <class I, class II> void test_pairhash()
|
|
||||||
{
|
|
||||||
const constexpr size_t nums = 1000;
|
|
||||||
I A[nums] = {0}, B[nums] = {0};
|
|
||||||
std::unordered_set<I> CH;
|
|
||||||
std::unordered_map<II, std::pair<I, I>> ints;
|
|
||||||
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 gen(rd());
|
|
||||||
|
|
||||||
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
|
||||||
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
|
||||||
|
|
||||||
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
|
||||||
if (std::is_signed<I>::value) bits -= 1;
|
|
||||||
const I Imin = 0;
|
|
||||||
const I Imax = I(std::pow(2., bits) - 1);
|
|
||||||
|
|
||||||
std::uniform_int_distribution<I> dis(Imin, Imax);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < nums;) {
|
|
||||||
I a = dis(gen);
|
|
||||||
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < nums;) {
|
|
||||||
I b = dis(gen);
|
|
||||||
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < nums; ++i) {
|
|
||||||
I a = A[i], b = B[i];
|
|
||||||
|
|
||||||
REQUIRE(a != b);
|
|
||||||
|
|
||||||
II hash_ab = sla::pairhash<I, II>(a, b);
|
|
||||||
II hash_ba = sla::pairhash<I, II>(b, a);
|
|
||||||
REQUIRE(hash_ab == hash_ba);
|
|
||||||
|
|
||||||
auto it = ints.find(hash_ab);
|
|
||||||
|
|
||||||
if (it != ints.end()) {
|
|
||||||
REQUIRE((
|
|
||||||
(it->second.first == a && it->second.second == b) ||
|
|
||||||
(it->second.first == b && it->second.second == a)
|
|
||||||
));
|
|
||||||
} else
|
|
||||||
ints[hash_ab] = std::make_pair(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SLA Raster test utils:
|
// SLA Raster test utils:
|
||||||
|
|
||||||
using TPixel = uint8_t;
|
using TPixel = uint8_t;
|
||||||
|
Loading…
Reference in New Issue
Block a user