mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-16 15:45:55 +08:00
Improved performance of collision detection with the build volume.
This commit is contained in:
parent
b61a5a5499
commit
6025ff492b
@ -76,8 +76,6 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_p
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "BuildVolume bed_shape clasified as: " << this->type_name();
|
BOOST_LOG_TRIVIAL(debug) << "BuildVolume bed_shape clasified as: " << this->type_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const float world_min_z = 0;
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
||||||
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
||||||
@ -187,41 +185,84 @@ static inline BuildVolume::ObjectState rectangle_test(const indexed_triangle_set
|
|||||||
// Trim the input transformed triangle mesh with print bed and test the remaining vertices with is_inside callback.
|
// Trim the input transformed triangle mesh with print bed and test the remaining vertices with is_inside callback.
|
||||||
// Return inside / colliding / outside state.
|
// Return inside / colliding / outside state.
|
||||||
template<typename InsideFn>
|
template<typename InsideFn>
|
||||||
BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, const Transform3f &trafo, InsideFn is_inside)
|
BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, InsideFn is_inside)
|
||||||
{
|
{
|
||||||
bool inside = false;
|
size_t num_inside = 0;
|
||||||
bool outside = false;
|
size_t num_above = 0;
|
||||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
bool inside = false;
|
||||||
const Vec3f pts[3] = { trafo * its.vertices[tri(0)], trafo * its.vertices[tri(1)], trafo * its.vertices[tri(2)] };
|
bool outside = false;
|
||||||
int iprev = 2;
|
static constexpr const auto world_min_z = float(-BuildVolume::SceneEpsilon);
|
||||||
for (int iedge = 0; iedge < 3; ++iedge) {
|
|
||||||
const Vec3f &p1 = pts[iprev];
|
if (may_be_below_bed)
|
||||||
const Vec3f &p2 = pts[iedge];
|
{
|
||||||
Vec3f pt;
|
// Slower test, needs to clip the object edges with the print bed plane.
|
||||||
if ((p1.z() < world_min_z && p2.z() > world_min_z) || (p2.z() < world_min_z && p1.z() > world_min_z)) {
|
// 1) Allocate transformed vertices with their position with respect to print bed surface.
|
||||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
std::vector<char> sides;
|
||||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
sides.reserve(its.vertices.size());
|
||||||
pt = Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z);
|
|
||||||
} else
|
const auto sign = [](const stl_vertex& pt) { return pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; };
|
||||||
pt = p2;
|
|
||||||
if (pt.z() >= world_min_z) {
|
for (const stl_vertex &v : its.vertices) {
|
||||||
if (is_inside(pt)) {
|
const stl_vertex pt = trafo * v;
|
||||||
inside = true;
|
const int s = sign(pt);
|
||||||
if (outside)
|
sides.emplace_back(s);
|
||||||
break;
|
if (s >= 0) {
|
||||||
} else {
|
// Vertex above or on print bed surface. Test whether it is inside the build volume.
|
||||||
outside = true;
|
++ num_above;
|
||||||
if (inside)
|
if (is_inside(pt))
|
||||||
|
++ num_inside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Calculate intersections of triangle edges with the build surface.
|
||||||
|
inside = num_inside > 0;
|
||||||
|
outside = num_inside < num_above;
|
||||||
|
if (num_above < its.vertices.size() && ! (inside && outside)) {
|
||||||
|
// Not completely above the build surface and status may still change by testing edges intersecting the build platform.
|
||||||
|
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||||
|
const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] };
|
||||||
|
if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) {
|
||||||
|
// Some edge of this triangle intersects the build platform. Calculate the intersection.
|
||||||
|
int iprev = 2;
|
||||||
|
for (int iedge = 0; iedge < 3; ++ iedge) {
|
||||||
|
if (s[iprev] * s[iedge] == -1) {
|
||||||
|
// edge intersects the build surface. Calculate intersection point.
|
||||||
|
const stl_vertex p1 = trafo * its.vertices[tri(iprev)];
|
||||||
|
const stl_vertex p2 = trafo * its.vertices[tri(iedge)];
|
||||||
|
assert(sign(p1) == s[iprev]);
|
||||||
|
assert(sign(p2) == s[iedge]);
|
||||||
|
assert(p1.z() * p2.z() < 0);
|
||||||
|
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||||
|
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||||
|
(is_inside(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)) ? inside : outside) = true;
|
||||||
|
}
|
||||||
|
iprev = iedge;
|
||||||
|
}
|
||||||
|
if (inside && outside)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
iprev = iedge;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Much simpler and faster code, not clipping the object with the print bed.
|
||||||
|
assert(! may_be_below_bed);
|
||||||
|
num_above = its.vertices.size();
|
||||||
|
for (const stl_vertex &v : its.vertices) {
|
||||||
|
const stl_vertex pt = trafo * v;
|
||||||
|
assert(pt.z() >= world_min_z);
|
||||||
|
if (is_inside(pt))
|
||||||
|
++ num_inside;
|
||||||
|
}
|
||||||
|
inside = num_inside > 0;
|
||||||
|
outside = num_inside < num_above;
|
||||||
|
}
|
||||||
|
|
||||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &its, const Transform3f &trafo) const
|
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const
|
||||||
{
|
{
|
||||||
switch (m_type) {
|
switch (m_type) {
|
||||||
case Type::Rectangle:
|
case Type::Rectangle:
|
||||||
@ -233,21 +274,21 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &i
|
|||||||
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||||
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
||||||
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
|
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||||
return object_state_templ(its, trafo, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
|
return object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
|
||||||
}
|
}
|
||||||
case Type::Circle:
|
case Type::Circle:
|
||||||
{
|
{
|
||||||
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
|
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
|
||||||
return m_max_print_height == 0 ?
|
return m_max_print_height == 0 ?
|
||||||
object_state_templ(its, trafo, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
|
object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
|
||||||
object_state_templ(its, trafo, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
||||||
}
|
}
|
||||||
case Type::Convex:
|
case Type::Convex:
|
||||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||||
case Type::Custom:
|
case Type::Custom:
|
||||||
return m_max_print_height == 0 ?
|
return m_max_print_height == 0 ?
|
||||||
object_state_templ(its, trafo, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
|
object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
|
||||||
object_state_templ(its, trafo, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
|
object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
|
||||||
case Type::Invalid:
|
case Type::Invalid:
|
||||||
default:
|
default:
|
||||||
return ObjectState::Inside;
|
return ObjectState::Inside;
|
||||||
@ -263,28 +304,6 @@ BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 &vol
|
|||||||
return build_volume.contains(volume_bbox) ? ObjectState::Inside : build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
return build_volume.contains(volume_bbox) ? ObjectState::Inside : build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildVolume::ObjectState BuildVolume::volume_state_convex(const TriangleMesh &volume_mesh, const Transform3f &trafo) const
|
|
||||||
{
|
|
||||||
assert(m_type == Type::Circle || m_type == Type::Convex || m_type == Type::Custom);
|
|
||||||
if (m_type == Type::Circle) {
|
|
||||||
const auto r2 = float(sqr(unscaled<double>(m_circle.radius + SceneEpsilon)));
|
|
||||||
const auto center = unscaled<float>(m_circle.center);
|
|
||||||
size_t outside = 0;
|
|
||||||
size_t valid = 0;
|
|
||||||
float max_z = m_max_print_height == 0 ? std::numeric_limits<float>::max() : m_max_print_height + float(SceneEpsilon);
|
|
||||||
for (const Vec3f &local_v : volume_mesh.its.vertices)
|
|
||||||
if (const Vec3f v = trafo * local_v; v.z() >= 0) {
|
|
||||||
++ valid;
|
|
||||||
if ((to_2d(v) - center).squaredNorm() > r2 || v.z() > max_z)
|
|
||||||
++ outside;
|
|
||||||
}
|
|
||||||
return outside == 0 ? ObjectState::Inside : outside < valid ? ObjectState::Colliding : ObjectState::Outside;
|
|
||||||
} else {
|
|
||||||
assert(m_type == Type::Convex);
|
|
||||||
return this->object_state(volume_mesh.its, trafo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const
|
bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const
|
||||||
{
|
{
|
||||||
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
||||||
|
@ -74,12 +74,10 @@ public:
|
|||||||
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
|
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
|
||||||
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
|
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
|
||||||
// Using SceneEpsilon
|
// Using SceneEpsilon
|
||||||
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo) const;
|
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const;
|
||||||
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
|
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
|
||||||
// Called for a rectangular bed:
|
// Called for a rectangular bed:
|
||||||
ObjectState volume_state_bbox(const BoundingBoxf3 &volume_bbox) const;
|
ObjectState volume_state_bbox(const BoundingBoxf3 &volume_bbox) const;
|
||||||
// Called for any other bed, volume_mesh is a convex hull trimmed by print bed plane.
|
|
||||||
ObjectState volume_state_convex(const TriangleMesh &volume_mesh, const Transform3f &trafo) const;
|
|
||||||
|
|
||||||
// 2) Test called on G-code paths.
|
// 2) Test called on G-code paths.
|
||||||
// Using BedEpsilon for all tests.
|
// Using BedEpsilon for all tests.
|
||||||
|
@ -1541,7 +1541,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
|
|||||||
for (const ModelVolume* vol : this->volumes)
|
for (const ModelVolume* vol : this->volumes)
|
||||||
if (vol->is_model_part()) {
|
if (vol->is_model_part()) {
|
||||||
const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix();
|
const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix();
|
||||||
BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast<float>());
|
BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast<float>(), true /* may be below print bed */);
|
||||||
if (state == BuildVolume::ObjectState::Inside)
|
if (state == BuildVolume::ObjectState::Inside)
|
||||||
inside_outside |= INSIDE;
|
inside_outside |= INSIDE;
|
||||||
else if (state == BuildVolume::ObjectState::Outside)
|
else if (state == BuildVolume::ObjectState::Outside)
|
||||||
|
@ -437,27 +437,55 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||||
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafo, double world_min_z) const
|
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafod, double world_min_z) const
|
||||||
{
|
{
|
||||||
BoundingBoxf3 bbox;
|
// 1) Allocate transformed vertices with their position with respect to print bed surface.
|
||||||
const Transform3f ftrafo = trafo.cast<float>();
|
std::vector<char> sides;
|
||||||
for (const stl_triangle_vertex_indices& tri : its.indices) {
|
size_t num_above = 0;
|
||||||
const Vec3f pts[3] = { ftrafo * its.vertices[tri(0)], ftrafo * its.vertices[tri(1)], ftrafo * its.vertices[tri(2)] };
|
Eigen::AlignedBox<float, 3> bbox;
|
||||||
int iprev = 2;
|
Transform3f trafo = trafod.cast<float>();
|
||||||
for (int iedge = 0; iedge < 3; ++iedge) {
|
sides.reserve(its.vertices.size());
|
||||||
const Vec3f& p1 = pts[iprev];
|
for (const stl_vertex &v : this->its.vertices) {
|
||||||
const Vec3f& p2 = pts[iedge];
|
const stl_vertex pt = trafo * v;
|
||||||
if ((p1.z() < world_min_z && p2.z() > world_min_z) || (p2.z() < world_min_z && p1.z() > world_min_z)) {
|
const int sign = pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0;
|
||||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
sides.emplace_back(sign);
|
||||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
if (sign >= 0) {
|
||||||
bbox.merge(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z).cast<double>());
|
// Vertex above or on print bed surface. Test whether it is inside the build volume.
|
||||||
}
|
++ num_above;
|
||||||
if (p2.z() >= world_min_z)
|
bbox.extend(pt);
|
||||||
bbox.merge(p2.cast<double>());
|
|
||||||
iprev = iedge;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bbox;
|
|
||||||
|
// 2) Calculate intersections of triangle edges with the build surface.
|
||||||
|
if (num_above < its.vertices.size()) {
|
||||||
|
// Not completely above the build surface and status may still change by testing edges intersecting the build platform.
|
||||||
|
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||||
|
const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] };
|
||||||
|
if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) {
|
||||||
|
// Some edge of this triangle intersects the build platform. Calculate the intersection.
|
||||||
|
int iprev = 2;
|
||||||
|
for (int iedge = 0; iedge < 3; ++ iedge) {
|
||||||
|
if (s[iprev] * s[iedge] == -1) {
|
||||||
|
// edge intersects the build surface. Calculate intersection point.
|
||||||
|
const stl_vertex p1 = trafo * its.vertices[tri(iprev)];
|
||||||
|
const stl_vertex p2 = trafo * its.vertices[tri(iedge)];
|
||||||
|
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||||
|
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||||
|
bbox.extend(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z));
|
||||||
|
}
|
||||||
|
iprev = iedge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundingBoxf3 out;
|
||||||
|
if (! bbox.isEmpty()) {
|
||||||
|
out.min = bbox.min().cast<double>();
|
||||||
|
out.max = bbox.max().cast<double>();
|
||||||
|
out.defined = true;
|
||||||
|
};
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
|
||||||
|
|
||||||
|
@ -969,7 +969,7 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo
|
|||||||
case BuildVolume::Type::Convex:
|
case BuildVolume::Type::Convex:
|
||||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||||
case BuildVolume::Type::Custom:
|
case BuildVolume::Type::Custom:
|
||||||
state = build_volume.volume_state_convex(volume_convex_mesh(*volume), volume->world_matrix().cast<float>());
|
state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast<float>(), volume_sinking(*volume));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Ignore, don't produce any collision.
|
// Ignore, don't produce any collision.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user