From 301a168b8998d6ffd8389af9729357cd771fc9e4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jun 2020 13:49:35 +0200 Subject: [PATCH] Fix bugs and non working tests Fix failing tests Try to fix build on windows Try to fix failng tests on Mac --- src/libslic3r/SLA/SupportTreeBuilder.cpp | 91 +++++---------------- src/libslic3r/SLA/SupportTreeBuilder.hpp | 33 ++------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 81 +++++++++++------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 13 +-- src/libslic3r/SLA/SupportTreeMesher.cpp | 72 ++++++++-------- src/libslic3r/SLA/SupportTreeMesher.hpp | 19 +++-- tests/sla_print/sla_test_utils.cpp | 4 +- 7 files changed, 132 insertions(+), 181 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index d4a9d00c99..9590936231 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include #include @@ -19,77 +21,8 @@ Head::Head(double r_big_mm, , width_mm(length_mm) , penetration_mm(penetration) { -// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); - - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) -// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } -//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): -// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) -//{ -// assert(steps > 0); - -// if(height > EPSILON) { // Endpoint is below the starting point - -// // We just create a bridge geometry with the pillar parameters and -// // move the data. -// Contour3D body = cylinder(radius, height, st, endp); -// mesh.points.swap(body.points); -// mesh.faces3.swap(body.faces3); -// } -//} - -//Pillar &Pillar::add_base(double baseheight, double radius) -//{ -// if(baseheight <= 0) return *this; -// if(baseheight > height) baseheight = height; - -// assert(steps >= 0); -// auto last = int(steps - 1); - -// if(radius < r ) radius = r; - -// double a = 2*PI/steps; -// double z = endpt(Z) + baseheight; - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + r*std::cos(phi); -// double y = endpt(Y) + r*std::sin(phi); -// base.points.emplace_back(x, y, z); -// } - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + radius*std::cos(phi); -// double y = endpt(Y) + radius*std::sin(phi); -// base.points.emplace_back(x, y, z - baseheight); -// } - -// auto ep = endpt; ep(Z) += baseheight; -// base.points.emplace_back(endpt); -// base.points.emplace_back(ep); - -// auto& indices = base.faces3; -// auto hcenter = int(base.points.size() - 1); -// auto lcenter = int(base.points.size() - 2); -// auto offs = int(steps); -// for(int i = 0; i < last; ++i) { -// indices.emplace_back(i, i + offs, offs + i + 1); -// indices.emplace_back(i, offs + i + 1, i + 1); -// indices.emplace_back(i, i + 1, hcenter); -// indices.emplace_back(lcenter, offs + i + 1, offs + i); -// } - -// indices.emplace_back(0, last, offs); -// indices.emplace_back(last, offs + last, offs); -// indices.emplace_back(hcenter, last, 0); -// indices.emplace_back(offs, offs + last, lcenter); -// return *this; -//} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -175,6 +108,18 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) return *this; } +void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius) +{ + std::lock_guard lk(m_mutex); + assert(pid >= 0 && size_t(pid) < m_pillars.size()); + Pillar& pll = m_pillars[size_t(pid)]; + m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height), + std::max(radius, pll.r), pll.r); + + m_pedestals.back().id = m_pedestals.size() - 1; + m_meshcache_valid = false; +} + const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const { if (m_meshcache_valid) return m_meshcache; @@ -192,6 +137,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const } for (auto &pedest : m_pedestals) { + if (ctl().stopcondition()) break; merged.merge(get_mesh(pedest, steps)); } @@ -209,7 +155,12 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const if (ctl().stopcondition()) break; merged.merge(get_mesh(bs, steps)); } - + + for (auto &anch : m_anchors) { + if (ctl().stopcondition()) break; + merged.merge(get_mesh(anch, steps)); + } + if (ctl().stopcondition()) { // In case of failure we have to return an empty mesh m_meshcache = TriangleMesh(); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index cc039de6fc..2b3ff91a06 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -91,12 +91,7 @@ struct Head { const Vec3d &direction = DOWN, // direction (normal to the dull end) const Vec3d &offset = {0, 0, 0} // displacement ); - - void transform() - { - // TODO: remove occurences - } - + inline double real_width() const { return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; @@ -119,13 +114,6 @@ struct Head { } }; -struct Join { - enum Types { - jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal, - jtPillarAnchor, jtBridgeAnchor - }; -}; - // A junction connecting bridges and pillars struct Junction { double r = 1; @@ -136,7 +124,6 @@ struct Junction { Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; - struct Pillar { double height, r; Vec3d endpt; @@ -162,18 +149,16 @@ struct Pillar { } const Vec3d& endpoint() const { return endpt; } - -// Pillar& add_base(double baseheight = 3, double radius = 2); }; // A base for pillars or bridges that end on the ground struct Pedestal { Vec3d pos; - double height, radius; + double height, r_bottom, r_top; long id = ID_UNSET; - Pedestal(const Vec3d &p, double h = 3., double r = 2.) - : pos{p}, height{h}, radius{r} + Pedestal(const Vec3d &p, double h, double rbottom, double rtop) + : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} {} }; @@ -301,15 +286,7 @@ public: return pillar.id; } - void add_pillar_base(long pid, double baseheight = 3, double radius = 2) - { - std::lock_guard lk(m_mutex); - assert(pid >= 0 && size_t(pid) < m_pillars.size()); - m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius); - m_pedestals.back().id = m_pedestals.size() - 1; - m_meshcache_valid = false; -// m_pillars[size_t(pid)].add_base(baseheight, radius); - } + void add_pillar_base(long pid, double baseheight = 3, double radius = 2); template const Anchor& add_anchor(Args&&...args) { diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 4b8366ee44..c6b2884d26 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -580,7 +580,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, bool normal_mode = true; Vec3d dir = sourcedir; - auto to_floor = [gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; if (m_cfg.object_elevation_mm < EPSILON) { @@ -599,6 +599,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Try to move along the established bridge direction to dodge the // forbidden region for the endpoint. double t = -radius; + bool succ = true; while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { t += radius; @@ -607,36 +608,58 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; + succ = false; + break; } } + + if (!succ) { + if (can_add_base) { + can_add_base = false; + base_r = 0.; + gndlvl -= m_mesh.ground_level_offset(); + min_dist = sd + base_r + EPSILON; + endp = {jp(X), jp(Y), gndlvl + radius}; + + t = -radius; + while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || + !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + t += radius; + endp = jp + t * dir; + normal_mode = false; + + if (t > m_cfg.max_bridge_length_mm || endp(Z) < (gndlvl + radius)) { + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); + return false; + } + } + } else return false; + } } + double h = (jp - endp).norm(); + // Check if the deduced route is sane and exit with error if not. - if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { + if (bridge_mesh_distance(jp, dir, radius) < h) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } // Straigh path down, no area to dodge if (normal_mode) { - double h = jp.z() - endp.z(); pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : - m_builder.add_pillar(jp, h, radius); + m_builder.add_pillar(endp, h, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); } else { // Insert the bridge to get around the forbidden area -// Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - double h = endp.z() - gndlvl; - pillar_id = m_builder.add_pillar(endp, h, radius); + Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + pillar_id = m_builder.add_pillar(pgnd, endp.z() - gndlvl, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); m_builder.add_bridge(jp, endp, radius); m_builder.add_junction(endp, radius); @@ -912,11 +935,8 @@ void SupportTreeBuildsteps::routing_to_ground() BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; m_iheads_onmodel.emplace_back(h.id); -// h.invalidate(); continue; } - - h.transform(); } // now we will go through the clusters ones again and connect the @@ -939,7 +959,6 @@ void SupportTreeBuildsteps::routing_to_ground() if (c == cidx) continue; auto &sidehead = m_builder.head(c); - sidehead.transform(); if (!connect_to_nearpillar(sidehead, centerpillarID) && !search_pillar_and_connect(sidehead)) { @@ -1016,6 +1035,12 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) if (it == m_head_to_ground_scans.end()) return false; auto &hit = it->second; + + if (!hit.is_hit()) { + // TODO scan for potential anchor points on model surface + return false; + } + Vec3d hjp = head.junction_point(); double zangle = std::asin(hit.direction()(Z)); zangle = std::max(zangle, PI/4); @@ -1033,13 +1058,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? center_hit.position() : hit.position(); - head.transform(); - - long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h); + long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z()); Pillar &pill = m_builder.pillar(pillar_id); Vec3d taildir = endp - hitp; - double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm; double w = dist - 2 * head.r_pin_mm - head.r_back_mm; if (w < 0.) { @@ -1050,12 +1073,6 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w, m_cfg.head_penetration_mm, taildir, hitp); -// Head tailhead(head.r_back_mm, head.r_pin_mm, w, -// m_cfg.head_penetration_mm, taildir, hitp); - -// tailhead.transform(); -// pill.base = tailhead.mesh; - m_pillar_index.guarded_insert(pill.endpoint(), pill.id); return true; @@ -1111,11 +1128,11 @@ void SupportTreeBuildsteps::routing_to_model() auto& head = m_builder.head(idx); // Search nearby pillar - if (search_pillar_and_connect(head)) { head.transform(); return; } + if (search_pillar_and_connect(head)) { return; } // Cannot connect to nearby pillar. We will try to search for // a route to the ground. - if (connect_to_ground(head)) { head.transform(); return; } + if (connect_to_ground(head)) { return; } // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } @@ -1300,12 +1317,14 @@ void SupportTreeBuildsteps::interconnect_pillars() if (found) for (unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; - Pillar p(s, s.z() - gnd, pillar().r); -// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + Vec3d s = spts[n]; + Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r); if (interconnect(pillar(), p)) { Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); + + add_pillar_base(pp.id); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); m_builder.add_junction(s, pillar().r); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index fc5670b163..51d8344480 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -17,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -inline Vec2d to_vec2(const Vec3d& v3) { - return {v3(X), v3(Y)}; -} +inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; } inline std::pair dir_to_spheric(const Vec3d &n, double norm = 1.) { @@ -47,7 +45,6 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } - // 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 @@ -297,8 +294,12 @@ class SupportTreeBuildsteps { const Vec3d &sourcedir, double radius, long head_id = ID_UNSET); - - + + void add_pillar_base(long pid) + { + m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); + } + public: SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 1d9be6c348..15491775b4 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -94,7 +94,7 @@ Contour3D sphere(double rho, Portion portion, double fa) { Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) { - assert(steps > 0); + assert(ssteps > 0); Contour3D ret; @@ -157,7 +157,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) { assert(steps > 0); - assert(length > 0.); + assert(length >= 0.); assert(r_back > 0.); assert(r_pin > 0.); @@ -167,7 +167,7 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // both circles perfectly. // Set up the model detail level - const double detail = 2*PI/steps; + const double detail = 2 * PI / steps; // We don't generate whole circles. Instead, we generate only the // portions which are visible (not covered by the robe) To know the @@ -176,26 +176,24 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // triangles the following relations: // The height of the whole mesh - const double h = r_back + r_pin + length; - double phi = PI / 2. - std::acos((r_back - r_pin) / h); + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); // To generate a whole circle we would pass a portion of (0, Pi) // To generate only a half horizontal circle we can pass (0, Pi/2) // The calculated phi is an offset to the half circles needed to smooth // the transition from the circle to the robe geometry - auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail); + auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail); - for(auto& p : s2.points) p.z() += h; + for (auto &p : s2.points) p.z() += h; mesh.merge(s1); mesh.merge(s2); - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { + for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; idx1++, idx2++) { coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; @@ -214,43 +212,43 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) return mesh; } -Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pos, + size_t steps) { assert(steps > 0); - if(baseheight <= 0) return {}; - - assert(steps >= 0); - auto last = int(steps - 1); + if (baseheight <= 0 || steps <= 0) return {}; Contour3D base; - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius * std::cos(phi); - double y = endpt(Y) + radius * std::sin(phi); - base.points.emplace_back(x, y, z); + double a = 2 * PI / steps; + auto last = int(steps - 1); + Vec3d ep{pos.x(), pos.y(), pos.z() + baseheight}; + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_top * std::cos(phi); + double y = pos.y() + r_top * std::sin(phi); + base.points.emplace_back(x, y, ep.z()); } - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_bottom * std::cos(phi); + double y = pos.y() + r_bottom * std::sin(phi); + base.points.emplace_back(x, y, pos.z()); } - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); + base.points.emplace_back(pos); base.points.emplace_back(ep); - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { + auto &indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for (int i = 0; i < last; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); indices.emplace_back(i, i + 1, hcenter); diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index 677cab3b81..a086680c34 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -24,23 +24,28 @@ Contour3D sphere(double rho, // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); +Contour3D cylinder(double r, + double h, + size_t steps = 45, + const Vec3d &sp = Vec3d::Zero()); Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); -Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pt = Vec3d::Zero(), + size_t steps = 45); inline Contour3D get_mesh(const Head &h, size_t steps) { Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); using Quaternion = Eigen::Quaternion; - // We rotate the head to the specified direction The head's pointing + // We rotate the head to the specified direction. The head's pointing // side is facing upwards so this means that it would hold a support // point with a normal pointing straight down. This is the reason of // the -1 z coordinate @@ -64,7 +69,7 @@ inline Contour3D get_mesh(const Pillar &p, size_t steps) inline Contour3D get_mesh(const Pedestal &p, size_t steps) { - return pedestal(p.pos, p.height, p.radius, steps); + return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps); } inline Contour3D get_mesh(const Junction &j, size_t steps) @@ -89,6 +94,6 @@ inline Contour3D get_mesh(const Bridge &br, size_t steps) return mesh; } -}} +}} // namespace Slic3r::sla #endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 5a3bd82a00..4cd94b7ed3 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -157,8 +157,8 @@ void test_supports(const std::string &obj_filename, if (std::abs(supportcfg.object_elevation_mm) < EPSILON) allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; - REQUIRE(obb.min.z() >= allowed_zmin); - REQUIRE(obb.max.z() <= zmax); + REQUIRE(obb.min.z() >= Approx(allowed_zmin)); + REQUIRE(obb.max.z() <= Approx(zmax)); // Move out the support tree into the byproducts, we can examine it further // in various tests.