WIP: Added a circle cursor and naive logic to select triangles inside

Deselection is possible when holding shift
Triangles obscured by the mesh are selected nonetheless (so far)
This commit is contained in:
Lukas Matena 2019-10-08 10:15:06 +02:00
parent 08daddb5de
commit 2e71dcefc3
4 changed files with 141 additions and 23 deletions

View file

@ -107,6 +107,7 @@ void GLGizmoFdmSupports::on_render() const
render_triangles(selection);
render_clipping_plane(selection);
render_cursor_circle();
glsafe(::glDisable(GL_BLEND));
}
@ -122,7 +123,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
::glColor3f(0.0f, 0.37f, 1.0f);
for (size_t facet_idx : m_selected_facets) {
for (size_t facet_idx=0; facet_idx<m_selected_facets.size(); ++facet_idx) {
if (! m_selected_facets[facet_idx])
continue;
stl_normal normal = 0.01f * MeshRaycaster::get_triangle_normal(m_mesh->its, facet_idx);
::glPushMatrix();
::glTranslatef(normal(0), normal(1), normal(2));
@ -135,9 +138,6 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
::glEnd();
::glPopMatrix();
}
}
void GLGizmoFdmSupports::render_clipping_plane(const Selection& selection) const
@ -170,6 +170,53 @@ void GLGizmoFdmSupports::render_clipping_plane(const Selection& selection) const
}
}
void GLGizmoFdmSupports::render_cursor_circle() const
{
const Camera& camera = m_parent.get_camera();
float zoom = (float)camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
std::cout << zoom << " " << inv_zoom << std::endl;
Size cnv_size = m_parent.get_canvas_size();
float cnv_half_width = 0.5f * (float)cnv_size.get_width();
float cnv_half_height = 0.5f * (float)cnv_size.get_height();
if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
return;
Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
center = center * inv_zoom;
glsafe(::glLineWidth(1.5f));
float color[3];
color[2] = 0.3f;
glsafe(::glColor3fv(color));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
// ensure that the circle is renderered inside the frustrum
glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = camera.get_gui_scale();
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
glsafe(::glPushAttrib(GL_ENABLE_BIT));
glsafe(::glLineStipple(4, 0xAAAA));
glsafe(::glEnable(GL_LINE_STIPPLE));
::glBegin(GL_LINE_LOOP);
for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
glsafe(::glEnd());
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
void GLGizmoFdmSupports::on_render_for_picking() const
{
@ -209,6 +256,20 @@ void GLGizmoFdmSupports::update_mesh()
m_mesh = &m_model_object->volumes.front()->mesh();
m_its = &m_mesh->its;
m_selected_facets.clear();
m_selected_facets.resize(m_mesh->its.indices.size());
// Prepare vector of vertex_index - facet_index pairs to quickly find adjacent facets
m_neighbors.clear();
m_neighbors.resize(3 * m_mesh->its.indices.size());
for (size_t i=0; i<m_mesh->its.indices.size(); ++i) {
const stl_triangle_vertex_indices& ind = m_mesh->its.indices[i];
m_neighbors.emplace_back(ind(0), i);
m_neighbors.emplace_back(ind(1), i);
m_neighbors.emplace_back(ind(2), i);
}
std::sort(m_neighbors.begin(), m_neighbors.end());
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster)
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
@ -218,9 +279,10 @@ void GLGizmoFdmSupports::update_mesh()
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
// Return false if no intersection was found, true otherwise.
bool GLGizmoFdmSupports::unproject_on_mesh(const Vec2d& mouse_pos, size_t& facet_idx)
// Unprojects the mouse position on the mesh and saves hit facet index into facet_idx
// Position of the hit in mesh coords is copied into *position, if provided.
// Returns false if no intersection was found, true otherwise.
bool GLGizmoFdmSupports::unproject_on_mesh(const Vec2d& mouse_pos, size_t& facet_idx, Vec3f* position)
{
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
if (! m_mesh_raycaster)
@ -235,12 +297,21 @@ bool GLGizmoFdmSupports::unproject_on_mesh(const Vec2d& mouse_pos, size_t& facet
// The raycaster query
Vec3f hit;
Vec3f normal;
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get(), &facet_idx))
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get(), &facet_idx)) {
if (position)
*position = hit;
return true;
}
else
return false;
}
bool operator<(const GLGizmoFdmSupports::NeighborData& a, const GLGizmoFdmSupports::NeighborData& b) {
return a.first < b.first;
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
@ -266,9 +337,51 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (action == SLAGizmoEventType::LeftDown || (action == SLAGizmoEventType::Dragging && m_wait_for_up_event)) {
size_t facet_idx = 0;
if (unproject_on_mesh(mouse_position, facet_idx)) {
m_selected_facets.push_back(facet_idx);
Vec3f hit_pos;
if (unproject_on_mesh(mouse_position, facet_idx, &hit_pos)) {
bool select = ! shift_down;
// Calculate direction from camera to the hit (in mesh coords):
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Geometry::Transformation trafo = volume->get_instance_transformation();
trafo.set_offset(trafo.get_offset());
Vec3f dir = ((trafo.get_matrix().inverse() * m_parent.get_camera().get_position()).cast<float>() - hit_pos).normalized();
// Calculate how far can a point be from the line (in mesh coords).
// FIXME: This should account for (possibly non-uniform) scaling of the mesh.
float limit = pow(m_cursor_radius, 2.f);
// A lambda to calculate distance from the line:
auto squared_distance_from_line = [&hit_pos, &dir](const Vec3f point) -> float {
Vec3f diff = hit_pos - point;
return (diff - diff.dot(dir) * dir).squaredNorm();
};
// Now go through the facets and de/select those close enough to the line (FIXME: efficiency)
for (size_t i=0; i<m_selected_facets.size(); ++i) {
float dist = squared_distance_from_line(m_mesh->its.vertices[m_mesh->its.indices[i](0)]);
if (dist < limit)
m_selected_facets[i] = select;
}
//size_t vertex_idx = m_mesh->its.indices[facet_idx](0);
//auto it = m_neighbors.begin();
//std::vector<bool> visited(m_mesh->its.indices.size(), false);
//while (it != m_neighbors.end() && it->second == facet_idx)
// it = std::lower_bound(it, m_neighbors.end(), std::make_pair(vertex_idx, size_t(0)));
//facet_idx = it->second;
//vertex_idx = m_mesh->its.indices[facet_idx](0);
m_wait_for_up_event = true;
m_parent.set_as_dirty();
return true;
}
if (action == SLAGizmoEventType::Dragging && m_wait_for_up_event)
@ -294,8 +407,6 @@ ClippingPlane GLGizmoFdmSupports::get_fdm_clipping_plane() const
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
}
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_model_object)
@ -337,6 +448,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"))
update_clipping_plane(true);
ImGui::SliderFloat(" ", &m_cursor_radius, 0.f, 8.f, "%.2f");
m_imgui->end();
}

View file

@ -21,7 +21,7 @@ private:
ObjectID m_model_object_id = 0;
int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb
bool unproject_on_mesh(const Vec2d& mouse_pos, size_t& facet_idx);
bool unproject_on_mesh(const Vec2d& mouse_pos, size_t& facet_idx, Vec3f* position = nullptr);
GLUquadricObj* m_quadric;
@ -30,9 +30,9 @@ private:
const TriangleMesh* m_mesh;
const indexed_triangle_set* m_its;
mutable std::vector<Vec2f> m_triangles;
float m_cursor_radius = 2.f;
std::vector<size_t> m_selected_facets;
std::vector<bool> m_selected_facets;
public:
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -40,6 +40,7 @@ public:
void set_fdm_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
ClippingPlane get_fdm_clipping_plane() const;
using NeighborData = std::pair<size_t, size_t>;
private:
@ -49,6 +50,7 @@ private:
void render_triangles(const Selection& selection) const;
void render_clipping_plane(const Selection& selection) const;
void render_cursor_circle() const;
bool is_mesh_update_necessary() const;
void update_mesh();
@ -64,6 +66,8 @@ private:
mutable std::unique_ptr<MeshClipper> m_object_clipper;
std::vector<NeighborData> m_neighbors; // pairs of vertex_index - facet_index
bool is_point_clipped(const Vec3d& point) const;
void update_clipping_plane(bool keep_normal = false) const;

View file

@ -85,11 +85,11 @@ void MeshClipper::recalculate_triangles()
tr = m_trafo.get_matrix().cast<float>() * tr;
m_triangles3d.clear();
m_triangles3d.reserve(m_triangles2d.size());
for (const Vec2f& pt : m_triangles2d) {
m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
m_triangles3d.back() = tr * m_triangles3d.back();
}
m_triangles3d.reserve(m_triangles2d.size());
for (const Vec2f& pt : m_triangles2d) {
m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
m_triangles3d.back() = tr * m_triangles3d.back();
}
m_triangles_valid = true;
}
@ -102,9 +102,8 @@ Vec3f MeshRaycaster::get_triangle_normal(const indexed_triangle_set& its, size_t
return Vec3f(a.cross(b)).normalized();
}
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
size_t* facet_idx) const
void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& point, Vec3d& direction) const
{
const std::array<int, 4>& viewport = camera.get_viewport();
const Transform3d& model_mat = camera.get_view_matrix();

View file

@ -113,6 +113,9 @@ public:
: m_emesh(mesh)
{}
void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& point, Vec3d& direction) const;
// Given a mouse position, this returns true in case it is on the mesh.
bool unproject_on_mesh(
const Vec2d& mouse_pos,