Use of bounding box of rotated 3D convex hull for out of print volume detection

This commit is contained in:
Enrico Turri 2018-08-15 12:50:06 +02:00
parent 211790f8c3
commit 4d98d32199
12 changed files with 276 additions and 141 deletions

View file

@ -1491,6 +1491,7 @@ namespace Slic3r {
stl_get_size(&stl);
volume->mesh.repair();
volume->calculate_convex_hull();
// apply volume's name and config data
for (const Metadata& metadata : volume_data.metadata)

View file

@ -406,6 +406,7 @@ void AMFParserContext::endElement(const char * /* name */)
}
stl_get_size(&stl);
m_volume->mesh.repair();
m_volume->calculate_convex_hull();
m_volume_facets.clear();
m_volume = nullptr;
break;

View file

@ -17,6 +17,11 @@
#include "SVG.hpp"
#include <Eigen/Dense>
static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f };
namespace Slic3r {
unsigned int Model::s_auto_extruder_id = 1;
@ -235,14 +240,6 @@ BoundingBoxf3 Model::bounding_box() const
return bb;
}
BoundingBoxf3 Model::transformed_bounding_box() const
{
BoundingBoxf3 bb;
for (const ModelObject* obj : this->objects)
bb.merge(obj->tight_bounding_box(false));
return bb;
}
void Model::center_instances_around_point(const Pointf &point)
{
// BoundingBoxf3 bb = this->bounding_box();
@ -623,54 +620,6 @@ const BoundingBoxf3& ModelObject::bounding_box() const
return m_bounding_box;
}
BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
{
BoundingBoxf3 bb;
for (const ModelVolume* vol : this->volumes)
{
if (include_modifiers || !vol->modifier)
{
for (const ModelInstance* inst : this->instances)
{
double c = cos(inst->rotation);
double s = sin(inst->rotation);
for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
{
const stl_facet& facet = vol->mesh.stl.facet_start[f];
for (int i = 0; i < 3; ++i)
{
// original point
const stl_vertex& v = facet.vertex[i];
Pointf3 p((double)v.x, (double)v.y, (double)v.z);
// scale
p.x *= inst->scaling_factor;
p.y *= inst->scaling_factor;
p.z *= inst->scaling_factor;
// rotate Z
double x = p.x;
double y = p.y;
p.x = c * x - s * y;
p.y = s * x + c * y;
// translate
p.x += inst->offset.x;
p.y += inst->offset.y;
bb.merge(p);
}
}
}
}
}
return bb;
}
// A mesh containing all transformed instances of this object.
TriangleMesh ModelObject::mesh() const
{
@ -755,15 +704,22 @@ void ModelObject::center_around_origin()
void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
{
for (ModelVolume *v : this->volumes)
{
v->mesh.translate(float(x), float(y), float(z));
if (m_bounding_box_valid)
v->m_convex_hull.translate(float(x), float(y), float(z));
}
if (m_bounding_box_valid)
m_bounding_box.translate(x, y, z);
}
void ModelObject::scale(const Pointf3 &versor)
{
for (ModelVolume *v : this->volumes)
{
v->mesh.scale(versor);
v->m_convex_hull.scale(versor);
}
// reset origin translation since it doesn't make sense anymore
this->origin_translation = Pointf3(0,0,0);
this->invalidate_bounding_box();
@ -774,6 +730,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
for (ModelVolume *v : this->volumes)
{
v->mesh.rotate(angle, axis);
v->m_convex_hull.rotate(angle, axis);
}
center_around_origin();
@ -790,6 +747,7 @@ void ModelObject::transform(const float* matrix3x4)
for (ModelVolume* v : volumes)
{
v->mesh.transform(matrix3x4);
v->m_convex_hull.transform(matrix3x4);
}
origin_translation = Pointf3(0.0, 0.0, 0.0);
@ -799,8 +757,12 @@ void ModelObject::transform(const float* matrix3x4)
void ModelObject::mirror(const Axis &axis)
{
for (ModelVolume *v : this->volumes)
{
v->mesh.mirror(axis);
this->origin_translation = Pointf3(0,0,0);
v->m_convex_hull.mirror(axis);
}
this->origin_translation = Pointf3(0, 0, 0);
this->invalidate_bounding_box();
}
@ -904,45 +866,20 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
{
for (ModelVolume* vol : this->volumes)
for (const ModelVolume* vol : this->volumes)
{
if (!vol->modifier)
{
for (ModelInstance* inst : this->instances)
{
BoundingBoxf3 bb;
std::vector<float> world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity();
m.translate(Eigen::Vector3f((float)inst->offset.x, (float)inst->offset.y, 0.0f));
m.rotate(Eigen::AngleAxisf(inst->rotation, Eigen::Vector3f::UnitZ()));
m.scale(inst->scaling_factor);
::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
double c = cos(inst->rotation);
double s = sin(inst->rotation);
for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
{
const stl_facet& facet = vol->mesh.stl.facet_start[f];
for (int i = 0; i < 3; ++i)
{
// original point
const stl_vertex& v = facet.vertex[i];
Pointf3 p((double)v.x, (double)v.y, (double)v.z);
// scale
p.x *= inst->scaling_factor;
p.y *= inst->scaling_factor;
p.z *= inst->scaling_factor;
// rotate Z
double x = p.x;
double y = p.y;
p.x = c * x - s * y;
p.y = s * x + c * y;
// translate
p.x += inst->offset.x;
p.y += inst->offset.y;
bb.merge(p);
}
}
BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(world_mat);
if (print_volume.contains(bb))
inst->print_volume_state = ModelInstance::PVS_Inside;
@ -1025,6 +962,16 @@ ModelMaterial* ModelVolume::assign_unique_material()
return model->add_material(this->_material_id);
}
void ModelVolume::calculate_convex_hull()
{
m_convex_hull = mesh.convex_hull_3d();
}
const TriangleMesh& ModelVolume::get_convex_hull() const
{
return m_convex_hull;
}
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.

View file

@ -105,9 +105,6 @@ public:
// This bounding box is being cached.
const BoundingBoxf3& bounding_box() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; }
// Returns a snug bounding box of the transformed instances.
// This bounding box is not being cached.
BoundingBoxf3 tight_bounding_box(bool include_modifiers) const;
// A mesh containing all transformed instances of this object.
TriangleMesh mesh() const;
@ -157,6 +154,10 @@ private:
class ModelVolume
{
friend class ModelObject;
// The convex hull of this model's mesh.
TriangleMesh m_convex_hull;
public:
std::string name;
// The triangular model.
@ -180,19 +181,32 @@ public:
ModelMaterial* assign_unique_material();
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
private:
// Parent object owning this ModelVolume.
ModelObject* object;
t_model_material_id _material_id;
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {}
ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) :
name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object)
{ this->material_id(other.material_id()); }
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object)
{
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) :
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object)
{
this->material_id(other.material_id());
}
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object)
{ this->material_id(other.material_id()); }
{
this->material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
};
// A single instance of a ModelObject.
@ -285,8 +299,6 @@ public:
bool add_default_instances();
// Returns approximate axis aligned bounding box of this model
BoundingBoxf3 bounding_box() const;
// Returns tight axis aligned bounding box of this model
BoundingBoxf3 transformed_bounding_box() const;
void center_instances_around_point(const Pointf &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;

View file

@ -1,6 +1,9 @@
#include "TriangleMesh.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "qhull/src/libqhullcpp/Qhull.h"
#include "qhull/src/libqhullcpp/QhullFacetList.h"
#include "qhull/src/libqhullcpp/QhullVertexSet.h"
#include <cmath>
#include <deque>
#include <queue>
@ -15,6 +18,8 @@
#include <tbb/parallel_for.h>
#include <Eigen/Dense>
#if 0
#define DEBUG
#define _DEBUG
@ -597,6 +602,134 @@ TriangleMesh::bounding_box() const
return bb;
}
BoundingBoxf3 TriangleMesh::transformed_bounding_box(const std::vector<float>& matrix) const
{
bool has_shared = (stl.v_shared != nullptr);
if (!has_shared)
stl_generate_shared_vertices(&stl);
unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets;
if (vertices_count == 0)
return BoundingBoxf3();
Eigen::MatrixXf src_vertices(3, vertices_count);
if (stl.stats.shared_vertices > 0)
{
stl_vertex* vertex_ptr = stl.v_shared;
for (int i = 0; i < stl.stats.shared_vertices; ++i)
{
src_vertices(0, i) = vertex_ptr->x;
src_vertices(1, i) = vertex_ptr->y;
src_vertices(2, i) = vertex_ptr->z;
vertex_ptr += 1;
}
}
else
{
stl_facet* facet_ptr = stl.facet_start;
unsigned int v_id = 0;
while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
{
for (int i = 0; i < 3; ++i)
{
src_vertices(0, v_id) = facet_ptr->vertex[i].x;
src_vertices(1, v_id) = facet_ptr->vertex[i].y;
src_vertices(2, v_id) = facet_ptr->vertex[i].z;
}
facet_ptr += 1;
++v_id;
}
}
if (!has_shared && (stl.stats.shared_vertices > 0))
stl_invalidate_shared_vertices(&stl);
Eigen::Transform<float, 3, Eigen::Affine> m;
::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float));
Eigen::MatrixXf dst_vertices(3, vertices_count);
dst_vertices = m * src_vertices.colwise().homogeneous();
float min_x = dst_vertices(0, 0);
float max_x = dst_vertices(0, 0);
float min_y = dst_vertices(1, 0);
float max_y = dst_vertices(1, 0);
float min_z = dst_vertices(2, 0);
float max_z = dst_vertices(2, 0);
for (int i = 1; i < vertices_count; ++i)
{
min_x = std::min(min_x, dst_vertices(0, i));
max_x = std::max(max_x, dst_vertices(0, i));
min_y = std::min(min_y, dst_vertices(1, i));
max_y = std::max(max_y, dst_vertices(1, i));
min_z = std::min(min_z, dst_vertices(2, i));
max_z = std::max(max_z, dst_vertices(2, i));
}
return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
}
TriangleMesh TriangleMesh::convex_hull_3d() const
{
// Helper struct for qhull:
struct PointForQHull{
PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {}
realT x, y, z;
};
std::vector<PointForQHull> src_vertices;
// We will now fill the vector with input points for computation:
stl_facet* facet_ptr = stl.facet_start;
while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
{
for (int i = 0; i < 3; ++i)
{
const stl_vertex& v = facet_ptr->vertex[i];
src_vertices.emplace_back(v.x, v.y, v.z);
}
facet_ptr += 1;
}
// The qhull call:
orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet
try
{
qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt");
}
catch (...)
{
std::cout << "Unable to create convex hull" << std::endl;
return TriangleMesh();
}
// Let's collect results:
Pointf3s det_vertices;
std::vector<Point3> facets;
auto facet_list = qhull.facetList().toStdVector();
for (const orgQhull::QhullFacet& facet : facet_list)
{ // iterate through facets
orgQhull::QhullVertexSet vertices = facet.vertices();
for (int i = 0; i < 3; ++i)
{ // iterate through facet's vertices
orgQhull::QhullPoint p = vertices[i].point();
const float* coords = p.coordinates();
det_vertices.emplace_back(coords[0], coords[1], coords[2]);
}
unsigned int size = (unsigned int)det_vertices.size();
facets.emplace_back(size - 3, size - 2, size - 1);
}
TriangleMesh output_mesh(det_vertices, facets);
output_mesh.repair();
return output_mesh;
}
void
TriangleMesh::require_shared_vertices()
{

View file

@ -55,6 +55,10 @@ public:
ExPolygons horizontal_projection() const;
Polygon convex_hull();
BoundingBoxf3 bounding_box() const;
// Returns the bbox of this TriangleMesh transformed by the given matrix
BoundingBoxf3 transformed_bounding_box(const std::vector<float>& matrix) const;
// Returns the convex hull of this TriangleMesh
TriangleMesh convex_hull_3d() const;
void reset_repair_stats();
bool needed_repair() const;
size_t facets_count() const;
@ -66,7 +70,7 @@ public:
// Count disconnected triangle patches.
size_t number_of_patches() const;
stl_file stl;
mutable stl_file stl;
bool repaired;
private:

View file

@ -202,7 +202,8 @@ const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
GLVolume::GLVolume(float r, float g, float b, float a)
: m_angle_z(0.0f)
, m_scale_factor(1.0f)
, m_dirty(true)
, m_transformed_bounding_box_dirty(true)
, m_transformed_convex_hull_bounding_box_dirty(true)
, composite_id(-1)
, select_group_id(-1)
, drag_group_id(-1)
@ -219,8 +220,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, tverts_range(0, size_t(-1))
, qverts_range(0, size_t(-1))
{
m_world_mat = std::vector<float>(UNIT_MATRIX, std::end(UNIT_MATRIX));
color[0] = r;
color[1] = g;
color[2] = b;
@ -264,45 +263,76 @@ const Pointf3& GLVolume::get_origin() const
void GLVolume::set_origin(const Pointf3& origin)
{
m_origin = origin;
m_dirty = true;
if (m_origin != origin)
{
m_origin = origin;
m_transformed_bounding_box_dirty = true;
m_transformed_convex_hull_bounding_box_dirty = true;
}
}
void GLVolume::set_angle_z(float angle_z)
{
m_angle_z = angle_z;
m_dirty = true;
if (m_angle_z != angle_z)
{
m_angle_z = angle_z;
m_transformed_bounding_box_dirty = true;
m_transformed_convex_hull_bounding_box_dirty = true;
}
}
void GLVolume::set_scale_factor(float scale_factor)
{
m_scale_factor = scale_factor;
m_dirty = true;
if (m_scale_factor != scale_factor)
{
m_scale_factor = scale_factor;
m_transformed_bounding_box_dirty = true;
m_transformed_convex_hull_bounding_box_dirty = true;
}
}
const std::vector<float>& GLVolume::world_matrix() const
void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
{
if (m_dirty)
{
Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity();
m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
m.scale(m_scale_factor);
::memcpy((void*)m_world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
m_dirty = false;
}
m_convex_hull = convex_hull;
}
return m_world_mat;
std::vector<float> GLVolume::world_matrix() const
{
std::vector<float> world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity();
m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
m.scale(m_scale_factor);
::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
return world_mat;
}
BoundingBoxf3 GLVolume::transformed_bounding_box() const
{
if (m_dirty)
if (m_transformed_bounding_box_dirty)
{
m_transformed_bounding_box = bounding_box.transformed(world_matrix());
m_transformed_bounding_box_dirty = false;
}
return m_transformed_bounding_box;
}
BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
{
if (m_transformed_convex_hull_bounding_box_dirty)
{
if (m_convex_hull.stl.stats.number_of_facets > 0)
m_transformed_convex_hull_bounding_box = m_convex_hull.transformed_bounding_box(world_matrix());
else
m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
m_transformed_convex_hull_bounding_box_dirty = false;
}
return m_transformed_convex_hull_bounding_box;
}
void GLVolume::set_range(double min_z, double max_z)
{
this->qverts_range.first = 0;
@ -629,6 +659,7 @@ std::vector<int> GLVolumeCollection::load_object(
if (!model_volume->modifier)
{
v.set_convex_hull(model_volume->get_convex_hull());
v.layer_height_texture = layer_height_texture;
if (extruder_id != -1)
v.extruder_id = extruder_id;
@ -716,6 +747,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.drag_group_id = obj_idx * 1000;
v.is_wipe_tower = true;
v.shader_outside_printer_detection_enabled = ! size_unknown;
v.set_convex_hull(mesh.convex_hull_3d());
return int(this->volumes.size() - 1);
}
@ -803,7 +835,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
{
if ((volume != nullptr) && !volume->is_modifier)
{
const BoundingBoxf3& bb = volume->transformed_bounding_box();
const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
bool contained = print_volume.contains(bb);
all_contained &= contained;

View file

@ -260,12 +260,16 @@ private:
float m_angle_z;
// Scale factor of the volume to be rendered.
float m_scale_factor;
// World matrix of the volume to be rendered.
std::vector<float> m_world_mat;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_bounding_box;
// Whether or not is needed to recalculate the world matrix.
mutable bool m_dirty;
// Whether or not is needed to recalculate the transformed bounding box.
mutable bool m_transformed_bounding_box_dirty;
// Convex hull of the original mesh, if any.
TriangleMesh m_convex_hull;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
// Whether or not is needed to recalculate the transformed convex hull bounding box.
mutable bool m_transformed_convex_hull_bounding_box_dirty;
public:
@ -323,13 +327,15 @@ public:
void set_origin(const Pointf3& origin);
void set_angle_z(float angle_z);
void set_scale_factor(float scale_factor);
void set_convex_hull(const TriangleMesh& convex_hull);
int object_idx() const { return this->composite_id / 1000000; }
int volume_idx() const { return (this->composite_id / 1000) % 1000; }
int instance_idx() const { return this->composite_id % 1000; }
const std::vector<float>& world_matrix() const;
std::vector<float> world_matrix() const;
BoundingBoxf3 transformed_bounding_box() const;
BoundingBoxf3 transformed_convex_hull_bounding_box() const;
bool empty() const { return this->indexed_vertex_array.empty(); }
bool indexed() const { return this->indexed_vertex_array.indexed(); }

View file

@ -3243,7 +3243,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
{
for (const GLVolume* volume : selected_volumes)
{
bb.merge(volume->transformed_bounding_box());
bb.merge(volume->transformed_convex_hull_bounding_box());
}
}

View file

@ -189,7 +189,7 @@ GLGizmoRotate::GLGizmoRotate()
, m_angle_z(0.0f)
, m_center(Pointf(0.0, 0.0))
, m_radius(0.0f)
, m_keep_radius(false)
, m_keep_initial_values(false)
{
}
@ -229,7 +229,7 @@ bool GLGizmoRotate::on_init()
void GLGizmoRotate::on_set_state()
{
m_keep_radius = (m_state == On) ? false : true;
m_keep_initial_values = (m_state == On) ? false : true;
}
void GLGizmoRotate::on_update(const Pointf& mouse_pos)
@ -255,19 +255,19 @@ void GLGizmoRotate::on_update(const Pointf& mouse_pos)
void GLGizmoRotate::on_refresh()
{
m_keep_radius = false;
m_keep_initial_values = false;
}
void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
{
::glDisable(GL_DEPTH_TEST);
const Pointf3& size = box.size();
m_center = box.center();
if (!m_keep_radius)
if (!m_keep_initial_values)
{
const Pointf3& size = box.size();
m_center = box.center();
m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
m_keep_radius = true;
m_keep_initial_values = true;
}
::glLineWidth(2.0f);

View file

@ -101,7 +101,7 @@ class GLGizmoRotate : public GLGizmoBase
mutable Pointf m_center;
mutable float m_radius;
mutable bool m_keep_radius;
mutable bool m_keep_initial_values;
public:
GLGizmoRotate();

View file

@ -65,7 +65,6 @@
%};
Clone<BoundingBoxf3> bounding_box() const
%code%{ RETVAL = THIS->bounding_box; %};
Clone<BoundingBoxf3> transformed_bounding_box() const;
bool empty() const;
bool indexed() const;