BambuStudio/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp

1865 lines
87 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoPainterBase.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <boost/log/trivial.hpp>
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include <memory>
#include <optional>
#include <imgui/imgui_internal.h>
namespace Slic3r::GUI {
GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{
// Make sphere and save it into a vertex buffer.
m_vbo_sphere.load_its_flat_shading(its_make_sphere(1., (2*M_PI)/24.));
m_vbo_sphere.finalize_geometry(true);
}
void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
{
if (m_state != On)
return;
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
if (mo && selection.is_from_single_instance()
&& (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
{
//BBS: add logic to distinguish the first_time_update and later_update
update_from_model_object(!m_schedule_update);
m_old_mo_id = mo->id();
m_old_volumes_size = mo->volumes.size();
m_schedule_update = false;
}
}
GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const
{
ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}};
// Take care of the clipping plane. The normal of the clipping plane is
// saved with opposite sign than we need to pass to OpenGL (FIXME)
if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) {
const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane();
for (size_t i = 0; i < 3; ++i)
clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]);
clp_data_out.clp_dataf[3] = float(clp->get_data()[3]);
}
// z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
if (m_c->get_canvas()->get_use_clipping_planes()) {
const std::array<ClippingPlane, 2> &clps = m_c->get_canvas()->get_clipping_planes();
clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])};
}
return clp_data_out;
}
void GLGizmoPainterBase::render_triangles(const Selection& selection) const
{
auto* shader = wxGetApp().get_shader("gouraud");
if (! shader)
return;
shader->start_using();
shader->set_uniform("slope.actived", false);
shader->set_uniform("print_volume.type", 0);
shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject *mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
Transform3d trafo_matrix;
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
trafo_matrix = mo->instances[selection.get_instance_idx()]->get_assemble_transformation().get_matrix() * mv->get_matrix();
trafo_matrix.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mo->instances[selection.get_instance_idx()]->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
}
else {
trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix()* mv->get_matrix();
}
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
// For printers with multiple extruders, it is necessary to pass trafo_matrix
// to the shader input variable print_box.volume_world_matrix before
// rendering the painted triangles. When this matrix is not set, the
// wrong transformation matrix is used for "Clipping of view".
shader->set_uniform("volume_world_matrix", trafo_matrix);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
void GLGizmoPainterBase::render_cursor() const
{
// First check that the mouse pointer is on an object.
const ModelObject* mo = m_c->selection_info()->model_object();
const Selection& selection = m_parent.get_selection();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Camera& camera = wxGetApp().plater()->get_camera();
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part())
{
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
Transform3d temp = mi->get_assemble_transformation().get_matrix() * mv->get_matrix();
temp.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mi->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
trafo_matrices.emplace_back(temp);
}
else {
trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
}
}
}
if (m_is_cursor_in_imgui == false) {
// Raycast and return if there's no hit.
update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices);
}
else {
m_rr.mouse_position = m_parent.get_local_mouse_position();
}
if (m_rr.mesh_id == -1) {
m_is_cursor_in_imgui = false;
return;
}
if (m_tool_type == ToolType::BRUSH) {
if (m_cursor_type == TriangleSelector::SPHERE)
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else if (m_cursor_type == TriangleSelector::CIRCLE)
render_cursor_circle();
else if (m_cursor_type == TriangleSelector::HEIGHT_RANGE)
render_cursor_height_range(trafo_matrices[m_rr.mesh_id]);
}
}
void GLGizmoPainterBase::render_cursor_circle() const
{
const Camera &camera = wxGetApp().plater()->get_camera();
auto zoom = (float) camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
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));
// BBS
std::array<float, 4> render_color = this->get_cursor_hover_color();
if (m_button_down == Button::Left)
render_color = this->get_cursor_sphere_left_button_color();
else if (m_button_down == Button::Right)
render_color = this->get_cursor_sphere_right_button_color();
glsafe(::glColor4fv(render_color.data()));
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());
glsafe(::glEnable(GL_DEPTH_TEST));
}
void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
{
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo.data()));
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)));
glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
if (is_left_handed)
glFrontFace(GL_CW);
// BBS
std::array<float, 4> render_color = this->get_cursor_hover_color();
if (m_button_down == Button::Left)
render_color = this->get_cursor_sphere_left_button_color();
else if (m_button_down == Button::Right)
render_color = this->get_cursor_sphere_right_button_color();
glsafe(::glColor4fv(render_color.data()));
m_vbo_sphere.render();
if (is_left_handed)
glFrontFace(GL_CCW);
glsafe(::glPopMatrix());
}
Vec2i GLGizmoPainterBase::_3d_to_mouse(Vec3d pos_in_3d, const Camera &camera) const
{
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection = camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
auto screen_width = viewport(2);
auto screen_height = viewport(3);
Vec4d origin_ndc = projection * modelview * Vec4d(pos_in_3d[0], pos_in_3d[1], pos_in_3d[2], 1.0);
Vec4d standard_ndc = origin_ndc / origin_ndc.w();
Vec2i screen;
screen[0] = screen_width * (standard_ndc[0] + 1.0) / 2.0;
screen[1] = screen_height - screen_height * (standard_ndc[1] + 1.0) / 2.0;
return screen;
}
bool GLGizmoPainterBase::is_valid_height_range_cursor(float min_z, float max_z) const
{
if (m_cursor_z + m_cursor_height <= min_z || m_cursor_z >= max_z) {
return false;
}
return true;
}
// BBS
void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) const
{
float buf_size= ImGui::CalcTextSize("-100.00").x + ImGui::GetStyle().FramePadding.x;
const BoundingBoxf3 box = bounding_box();
Vec3d hit_world = trafo * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2));
float max_z = (float)box.max.z();
float min_z = (float)box.min.z();
if (m_is_cursor_in_imgui == false) {
m_cursor_z = std::clamp((float) hit_world.z(), min_z, max_z);
m_height_start_z_in_imgui = m_cursor_z;
}
ImGuiWrapper &imgui = *wxGetApp().imgui();
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0);
ImGui::PushStyleColor(ImGuiCol_WindowBg, m_is_dark_mode ? ImVec4(38 / 255.0, 46 / 255.0, 48 / 255.0, 0.7) : ImVec4(255 / 255.0, 255 / 255.0, 255 / 255.0, 0.7));
ImGui::SetNextWindowFocus();
imgui.set_next_window_pos(m_height_start_pos[0], m_height_start_pos[1], ImGuiCond_Always, 0.0f, 0.0f);
imgui.begin(wxString("cursor_height_range"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration);
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
ImGui::AlignTextToFramePadding();
ImGui::PushStyleColor(ImGuiCol_Text, m_is_dark_mode ? ImVec4(255 / 255.0, 255 / 255.0, 255 / 255.0, 0.7) : ImVec4(38 / 255.0, 46 / 255.0, 48 / 255.0, 0.7));
ImGui::TextUnformatted(_L("Bottom:").ToUTF8().data());
ImGui::SameLine();
ImGui::PushItemWidth(buf_size);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0);
ImGui::PushStyleColor(ImGuiCol_FrameBg, m_is_dark_mode ? ImVec4(38 / 255.0, 46 / 255.0, 48 / 255.0, 0.7) : ImVec4(255 / 255.0, 255 / 255.0, 255 / 255.0, 0.7));
ImGui::BBLInputDouble("##m_height_start_z_in_imgui", &m_height_start_z_in_imgui, 0.0f, 0.0f, "%.2f");
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);//for ImGuiCol_FrameBg
ImGui::PopStyleColor(1);//for Text
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, m_is_dark_mode ? ImVec4(38 / 255.0, 46 / 255.0, 48 / 255.0, 0.7) : ImVec4(255 / 255.0, 255 / 255.0, 255 / 255.0, 0.7));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, m_is_dark_mode ? ImVec4(50 / 255.0f, 58 / 255.0f, 61 / 255.0f, 1.00f) : ImVec4(238 / 255.0, 238 / 255.0, 238 / 255.0, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, m_is_dark_mode ? ImVec4(107 / 255.0f, 107 / 255.0f, 107 / 255.0f, 1.00f) : ImVec4(206 / 255.0f, 206 / 255.0f, 206 / 255.0f, 1.00f));
bool btn_clicked = ImGui::Button(into_u8(m_is_dark_mode ? ImGui::ConfirmDarkIcon : ImGui::ConfirmIcon).c_str());
ImGui::PopStyleColor(3);
auto imgui_size = ImGui::GetWindowSize();
const Camera &camera = wxGetApp().plater()->get_camera();
auto screen_height = Vec4i(camera.get_viewport().data())(3);
if (m_rr.mouse_position[0] >= m_height_start_pos[0] && m_rr.mouse_position[0] <= m_height_start_pos[0] + imgui_size[0]
&& m_rr.mouse_position[1] >= m_height_start_pos[1] && m_rr.mouse_position[1] <= m_height_start_pos[1] + imgui_size[1]) {
m_is_cursor_in_imgui = true;
m_cursor_z = m_height_start_z_in_imgui;
} else {
m_is_cursor_in_imgui = false;
ImGui::SetNextWindowFocus();
}
if (btn_clicked) {
if (is_valid_height_range_cursor(min_z, max_z)) {
m_is_set_height_start_z_by_imgui = true;
const_cast<GLGizmoPainterBase &>(*this).gizmo_event(SLAGizmoEventType::LeftDown, Vec2d(0, 0), false, false, false);
m_is_set_height_start_z_by_imgui = false;
}
m_rr.mesh_id = -1; // exit
}
imgui.set_requires_extra_frame();
imgui.end();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);
if (!is_valid_height_range_cursor(min_z, max_z)) {
return;
}
std::array<float, 2> zs = {m_cursor_z, std::clamp(m_cursor_z + m_cursor_height, min_z, max_z)};
const Selection& selection = m_parent.get_selection();
const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
const ModelInstance* mi = model_object->instances[selection.get_instance_idx()];
int volumes_count = model_object->volumes.size();
if (m_cut_contours.size() != volumes_count * 2) {
m_cut_contours.resize(volumes_count * 2);
}
m_volumes_index = 0;
for (const ModelVolume* mv : model_object->volumes) {
TriangleMesh vol_mesh = mv->mesh();
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
Transform3d temp = mi->get_assemble_transformation().get_matrix() * mv->get_matrix();
temp.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mi->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
vol_mesh.transform(temp);
}
else {
vol_mesh.transform(mi->get_transformation().get_matrix() * mv->get_matrix());
}
for (int i = 0; i < zs.size(); i++) {
update_contours(m_volumes_index, vol_mesh, zs[i], max_z, min_z, m_is_cursor_in_imgui ? false : (i == 0 ? true : false));
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_cut_contours[m_volumes_index].shift.x(), m_cut_contours[m_volumes_index].shift.y(), m_cut_contours[m_volumes_index].shift.z()));
glsafe(::glLineWidth(2.0f));
m_cut_contours[m_volumes_index].contours.render();
glsafe(::glPopMatrix());
m_volumes_index++;
}
}
}
BoundingBoxf3 GLGizmoPainterBase::bounding_box() const
{
BoundingBoxf3 ret;
const Selection& selection = m_parent.get_selection();
const Selection::IndicesList& idxs = selection.get_volume_idxs();
for (unsigned int i : idxs) {
const GLVolume* volume = selection.get_volume(i);
if (!volume->is_modifier)
ret.merge(volume->transformed_convex_hull_bounding_box());
}
return ret;
}
struct ScreenPosSort {
Vec2i pos_screen;
Vec3d pos_3d;
};
void GLGizmoPainterBase::update_contours(int i, const TriangleMesh &vol_mesh, float cursor_z, float max_z, float min_z, bool update_height_start_pos) const
{
const Selection& selection = m_parent.get_selection();
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();
const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
const int instance_idx = selection.get_instance_idx();
if (min_z < cursor_z && cursor_z < max_z) {
if (m_cut_contours[i].cut_z != cursor_z || m_cut_contours[i].object_id != model_object->id() || m_cut_contours[i].instance_idx != instance_idx) {
m_cut_contours[i].cut_z = cursor_z;
m_cut_contours[i].mesh = vol_mesh;
m_cut_contours[i].position = box.center();
m_cut_contours[i].shift = Vec3d::Zero();
m_cut_contours[i].object_id = model_object->id();
m_cut_contours[i].instance_idx = instance_idx;
m_cut_contours[i].contours.reset();
MeshSlicingParams slicing_params;
slicing_params.trafo = Transform3d::Identity().matrix();
const Polygons polys = slice_mesh(m_cut_contours[i].mesh.its, cursor_z, slicing_params);
if (!polys.empty()) {
if (update_height_start_pos) {
const Camera &camera = wxGetApp().plater()->get_camera();
std::vector<ScreenPosSort> screen_pos_sorts;
for (size_t i = 0; i < polys.size(); i++) {
for (size_t j = 0; j < polys[i].points.size(); j++) {
Vec2d pt = unscale(polys[i].points[j]).cast<double>();
Vec3d pos_3d(pt[0], pt[1], cursor_z);
Vec2i cur_screen_pos = _3d_to_mouse(pos_3d, camera);
if (cur_screen_pos[0] >= m_rr.mouse_position[0]) {
ScreenPosSort cur{cur_screen_pos, pos_3d};
screen_pos_sorts.emplace_back(cur);
}
}
}
std::sort(screen_pos_sorts.begin(), screen_pos_sorts.end(), [=](const ScreenPosSort &s1, const ScreenPosSort &s2) {
int threshold_x = 20;
int threshold_y = 20;
if (abs(s1.pos_screen.x() - s2.pos_screen.x()) < threshold_x && abs(s1.pos_screen.y() - s2.pos_screen.y()) > threshold_y) {
return abs(s1.pos_screen.y() - m_rr.mouse_position[1]) < abs(s2.pos_screen.y() - m_rr.mouse_position[1]);
} else {
return s1.pos_screen.x() > s2.pos_screen.x();
}
});
if(screen_pos_sorts.size() >= 1){
m_height_start_pos = screen_pos_sorts[0].pos_screen;
// make mouse to cover in a part of imgui
m_height_start_pos[0] -= 10;
m_height_start_pos[1] -= 10;
}
}
m_cut_contours[i].contours.init_from(polys, static_cast<float>(cursor_z));
m_cut_contours[i].contours.set_color(-1, {1.0f, 1.0f, 1.0f, 1.0f});
}
} else if (box.center() != m_cut_contours[i].position) {
m_cut_contours[i].shift = box.center() - m_cut_contours[i].position;
}
}
else
m_cut_contours[i].contours.reset();
}
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
{
if (m_c->object_clipper()->get_position() == 0.)
return false;
auto sel_info = m_c->selection_info();
Vec3d transformed_point = trafo * point;
transformed_point(2) += sel_info->get_sla_shift();
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
{
// List of mouse positions that will be used as seeds for painting.
std::vector<Vec2d> mouse_positions{mouse_position};
if (m_last_mouse_click != Vec2d::Zero()) {
// In case current mouse position is far from the last one,
// add several positions from between into the list, so there
// are no gaps in the painted region.
if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
mouse_positions.emplace_back(mouse_position + patch_idx * diff);
mouse_positions.emplace_back(m_last_mouse_click);
}
}
const Camera &camera = wxGetApp().plater()->get_camera();
std::vector<ProjectedMousePosition> mesh_hit_points;
mesh_hit_points.reserve(mouse_positions.size());
// In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
for (const Vec2d &mp : mouse_positions) {
update_raycast_cache(mp, camera, trafo_matrices);
mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
if (m_rr.mesh_id == -1)
break;
}
// Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
mesh_hit_points_by_mesh.emplace_back();
mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
prev_mesh_hit_point = next_mesh_hit_point;
}
}
auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
for (const ProjectedMousePosition &mesh_hit_point : hit_points)
if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
return false;
return true;
};
struct Plane
{
Vec3d origin;
Vec3d first_axis;
Vec3d second_axis;
};
auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
assert(hit_points.size() >= 3);
for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
const Vec3d first_vec = first_point - second_point;
const Vec3d second_vec = third_point - second_point;
// If three points aren't collinear, then there exists only one plane going through all points.
if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
const Vec3d first_axis_vec_n = first_vec.normalized();
// Make second_vec perpendicular to first_axis_vec_n using GramSchmidt orthogonalization process
const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
}
}
return std::nullopt;
};
for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
assert(!hit_points.empty());
if (hit_points.back().mesh_idx == -1)
break;
if (hit_points.size() <= 2)
continue;
if (on_same_facet(hit_points)) {
hit_points = {hit_points.front(), hit_points.back()};
} else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
Polyline polyline;
polyline.points.reserve(hit_points.size());
// Project hit_points into its plane to simplified them in the next step.
for (auto &hit_point : hit_points) {
const Vec3d &point = hit_point.mesh_hit.cast<double>();
const double x_cord = plane->first_axis.dot(point - plane->origin);
const double y_cord = plane->second_axis.dot(point - plane->origin);
polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
}
polyline.simplify(scale_(m_cursor_radius) / 10.);
const int mesh_idx = hit_points.front().mesh_idx;
std::vector<ProjectedMousePosition> new_hit_points;
new_hit_points.reserve(polyline.points.size());
// Project 2D simplified hit_points beck to 3D.
for (const Point &point : polyline.points) {
const double x_cord = unscale<double>(point.x());
const double y_cord = unscale<double>(point.y());
const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
}
hit_points = new_hit_points;
} else {
hit_points = {hit_points.front(), hit_points.back()};
}
}
return mesh_hit_points_by_mesh;
}
// BBS
std::vector<GLGizmoPainterBase::ProjectedHeightRange> GLGizmoPainterBase::get_projected_height_range(
const Vec2d& mouse_position,
double resolution,
const std::vector<const ModelVolume*>& part_volumes,
const std::vector<Transform3d>& trafo_matrices) const
{
std::vector<GLGizmoPainterBase::ProjectedHeightRange> hit_triangles_by_mesh;
float z_bot_world;
if (m_is_set_height_start_z_by_imgui == false) {
const Camera &camera = wxGetApp().plater()->get_camera();
// In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
update_raycast_cache(mouse_position, camera, trafo_matrices);
if (m_rr.mesh_id == -1) return hit_triangles_by_mesh;
ProjectedMousePosition mesh_hit_point = {m_rr.hit, m_rr.mesh_id, m_rr.facet};
z_bot_world = (trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2))).z();
} else {
z_bot_world = m_height_start_z_in_imgui;
}
float z_top_world = z_bot_world + m_cursor_height;
hit_triangles_by_mesh.push_back({z_bot_world, m_rr.mesh_id, size_t(m_rr.facet)});
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Transform3d instance_trafo = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix() :
mi->get_transformation().get_matrix();
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix(true) :
mi->get_transformation().get_matrix(true);
for (int mesh_idx = 0; mesh_idx < part_volumes.size(); mesh_idx++) {
if (mesh_idx == m_rr.mesh_id)
continue;
const Transform3d& trafo = trafo_matrices[mesh_idx];
const indexed_triangle_set& its = part_volumes[mesh_idx]->mesh().its;
int first_hit_facet_idx = -1;
for (int facet_idx = 0; facet_idx < its.indices.size(); facet_idx++) {
stl_vertex v0 = its.vertices[its.indices[facet_idx].x()];
stl_vertex v1 = its.vertices[its.indices[facet_idx].y()];
stl_vertex v2 = its.vertices[its.indices[facet_idx].z()];
float v0_z = (trafo * Vec3d(v0(0), v0(1), v0(2))).z();
float v1_z = (trafo * Vec3d(v1(0), v1(1), v1(2))).z();
float v2_z = (trafo * Vec3d(v2(0), v2(1), v2(2))).z();
bool outside_range = (v0_z < z_bot_world&& v1_z < z_bot_world&& v2_z < z_bot_world) ||
(v0_z > z_top_world && v1_z > z_top_world && v2_z > z_top_world);
if (!outside_range) {
first_hit_facet_idx = facet_idx;
break;
}
}
if (first_hit_facet_idx != -1) {
hit_triangles_by_mesh.push_back({ z_bot_world, mesh_idx, (size_t)first_hit_facet_idx });
}
}
return hit_triangles_by_mesh;
}
// 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
// concludes that the event was not intended for it, it should return false.
bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
Vec2d _mouse_position = mouse_position;
if (action == SLAGizmoEventType::MouseWheelUp
|| action == SLAGizmoEventType::MouseWheelDown) {
if (control_down) {
//BBS
if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::HEIGHT_RANGE) {
m_cursor_height = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_height - this->get_cursor_height_step(), this->get_cursor_height_min()) :
std::min(m_cursor_height + this->get_cursor_height_step(), this->get_cursor_height_max());
m_parent.set_as_dirty();
return true;
}
if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min()) :
std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
m_parent.set_as_dirty();
return true;
}
if (m_tool_type == ToolType::BUCKET_FILL || m_tool_type == ToolType::SMART_FILL) {
m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
m_parent.set_as_dirty();
if (m_rr.mesh_id != -1) {
const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d trafo_matrix_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true) :
mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
const Transform3d trafo_matrix = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix() :
mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
}
return true;
}
if (m_tool_type == ToolType::GAP_FILL) {
TriangleSelectorPatch::gap_area = action == SLAGizmoEventType::MouseWheelDown ?
std::max(TriangleSelectorPatch::gap_area - TriangleSelectorPatch::GapAreaStep, TriangleSelectorPatch::GapAreaMin) :
std::min(TriangleSelectorPatch::gap_area + TriangleSelectorPatch::GapAreaStep, TriangleSelectorPatch::GapAreaMax);
m_parent.set_as_dirty();
return true;
}
}
else if (alt_down) {
// BBS
double pos = m_c->object_clipper()->get_position();
pos = action == SLAGizmoEventType::MouseWheelDown
? std::max(0., pos - 0.01)
: std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true);
return true;
}
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false);
return true;
}
if (action == SLAGizmoEventType::LeftDown
|| action == SLAGizmoEventType::RightDown
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
if (m_triangle_selectors.empty())
return false;
EnforcerBlockerType new_state = EnforcerBlockerType::NONE;
// BBS
if (action == SLAGizmoEventType::Dragging) {
if (m_button_down == Button::Right && this->get_right_button_state_type() == EnforcerBlockerType(-1))
return false;
}
else {
if (action == SLAGizmoEventType::RightDown && this->get_right_button_state_type() == EnforcerBlockerType(-1))
return false;
}
if (! shift_down) {
if (action == SLAGizmoEventType::Dragging)
new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type();
else
new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
}
const Camera &camera = wxGetApp().plater()->get_camera();
const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
Transform3d instance_trafo = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix() :
mi->get_transformation().get_matrix();
Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix(true) :
mi->get_transformation().get_matrix(true);
std::vector<const ModelVolume*> part_volumes;
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
std::vector<Transform3d> trafo_matrices_not_translate;
for (const ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
Transform3d temp = instance_trafo * mv->get_matrix();
temp.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mi->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
trafo_matrices.emplace_back(temp);
}
else {
trafo_matrices.emplace_back(instance_trafo* mv->get_matrix());
}
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
part_volumes.push_back(mv);
}
// BBS
if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::HEIGHT_RANGE)
{
std::vector<ProjectedHeightRange> projected_height_range_by_mesh = get_projected_height_range(_mouse_position, 1., part_volumes, trafo_matrices);
m_last_mouse_click = Vec2d::Zero();
for (int i = 0; i < projected_height_range_by_mesh.size(); i++) {
const ProjectedHeightRange& phr = projected_height_range_by_mesh[i];
int mesh_idx = phr.mesh_idx;
// The mouse button click detection is enabled when there is a valid hit.
// Missing the object entirely
// shall not capture the mouse.
const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
if (mesh_idx != -1 && m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
const Transform3d& trafo_matrix = trafo_matrices[mesh_idx];
const Transform3d& trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
// Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
const TriangleSelector::ClippingPlane& clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(phr.z_world,
camera_pos, m_cursor_height, trafo_matrix, clp);
m_triangle_selectors[mesh_idx]->select_patch(int(phr.first_facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
m_triangle_selectors[mesh_idx]->request_update_render_data(true);
m_last_mouse_click = _mouse_position;
}
return true;
}
if (action == SLAGizmoEventType::Dragging && m_tool_type == ToolType::BRUSH) {
if (m_vertical_only)
_mouse_position.x() = m_last_mouse_click.x();
else if (m_horizontal_only)
_mouse_position.y() = m_last_mouse_click.y();
}
std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(_mouse_position, 1., trafo_matrices);
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
assert(!projected_mouse_positions.empty());
const int mesh_idx = projected_mouse_positions.front().mesh_idx;
const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
// The mouse button click detection is enabled when there is a valid hit.
// Missing the object entirely
// shall not capture the mouse.
if (mesh_idx != -1)
if (m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
// In case we have no valid hit, we can return. The event will be stopped when
// dragging while painting (to prevent scene rotations and moving the object)
if (mesh_idx == -1)
return dragging_while_painting;
const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
// Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
assert(mesh_idx < int(m_triangle_selectors.size()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
assert(projected_mouse_position.mesh_idx == mesh_idx);
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
const int facet_idx = int(projected_mouse_position.facet_idx);
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
if (m_tool_type == ToolType::SMART_FILL)
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
// BBS: add infill_angle parameter
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, -1.f, false, true);
else if (m_tool_type == ToolType::BUCKET_FILL)
// BBS: add infill_angle parameter
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, true, true);
m_seed_fill_last_mesh_id = -1;
}
} else if (m_tool_type == ToolType::BRUSH) {
assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
if (projected_mouse_positions.size() == 1) {
const ProjectedMousePosition& first_position = projected_mouse_positions.front();
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
camera_pos, m_cursor_radius,
m_cursor_type, trafo_matrix, clp);
m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
}
else {
for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
auto second_position_it = first_position_it + 1;
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
}
}
}
m_triangle_selectors[mesh_idx]->request_update_render_data(true);
m_last_mouse_click = _mouse_position;
}
return true;
}
if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
if (m_triangle_selectors.empty())
return false;
const Camera &camera = wxGetApp().plater()->get_camera();
const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d instance_trafo = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix() :
mi->get_transformation().get_matrix();
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mi->get_assemble_transformation().get_matrix(true) :
mi->get_transformation().get_matrix(true);
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
std::vector<Transform3d> trafo_matrices_not_translate;
for (const ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
Transform3d temp = instance_trafo * mv->get_matrix();
temp.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mi->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
trafo_matrices.emplace_back(temp);
}
else {
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
}
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
}
// Now "click" into all the prepared points and spill paint around them.
update_raycast_cache(_mouse_position, camera, trafo_matrices);
auto seed_fill_unselect_all = [this]() {
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
};
if (m_rr.mesh_id == -1) {
// Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh.
seed_fill_unselect_all();
m_seed_fill_last_mesh_id = -1;
// In case we have no valid hit, we can return.
return false;
}
// The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill.
if(m_rr.mesh_id != m_seed_fill_last_mesh_id)
seed_fill_unselect_all();
const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
if (m_tool_type == ToolType::SMART_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
// BBS: add infill_angle parameter
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, -1.f, false);
else if (m_tool_type == ToolType::BUCKET_FILL)
// BBS: add infill_angle parameter
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
return true;
}
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
&& m_button_down != Button::None) {
// Take snapshot and update ModelVolume data.
wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), std::string(action_name.ToUTF8().data()), UndoRedo::SnapshotType::GizmoAction);
update_model_object();
m_button_down = Button::None;
m_last_mouse_click = Vec2d::Zero();
return true;
}
return false;
}
void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
const Camera& camera,
const std::vector<Transform3d>& trafo_matrices) const
{
if (m_rr.mouse_position == mouse_position) {
// Same query as last time - the answer is already in the cache.
return;
}
Vec3f normal = Vec3f::Zero();
Vec3f hit = Vec3f::Zero();
size_t facet = 0;
Vec3f closest_hit = Vec3f::Zero();
double closest_hit_squared_distance = std::numeric_limits<double>::max();
size_t closest_facet = 0;
int closest_hit_mesh_id = -1;
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
mouse_position,
trafo_matrices[mesh_id],
camera,
hit,
normal,
m_c->object_clipper()->get_clipping_plane(),
&facet,
m_parent.get_canvas_type() != GLCanvas3D::CanvasAssembleView))
{
// In case this hit is clipped, skip it.
if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id]))
continue;
// Is this hit the closest to the camera so far?
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
if (hit_squared_distance < closest_hit_squared_distance) {
closest_hit_squared_distance = hit_squared_distance;
closest_facet = facet;
closest_hit_mesh_id = mesh_id;
closest_hit = hit;
}
}
}
m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet};
}
bool GLGizmoPainterBase::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|| !selection.is_single_full_instance()/* || wxGetApp().get_mode() == comSimple*/)
return false;
// BBS
#if 0
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
const Selection::IndicesList& list = selection.get_volume_idxs();
return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; });
#else
return true;
#endif
}
bool GLGizmoPainterBase::on_is_selectable() const
{
//BBS
/*return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
&& wxGetApp().get_mode() != comSimple );*/
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF);
}
CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::InstancesHider)
| int(CommonGizmosDataID::Raycaster)
| int(CommonGizmosDataID::ObjectClipper));
}
void GLGizmoPainterBase::on_set_state()
{
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
on_opening();
const Selection& selection = m_parent.get_selection();
//Camera& camera = wxGetApp().plater()->get_camera();
//Vec3d rotate_target = selection.get_bounding_box().center();
//rotate_target(2) = 0.f;
//Vec3d position = camera.get_position();
//camera.set_target(rotate_target);
//camera.look_at(position, rotate_target, Vec3d::UnitZ());
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
if (m_c->object_clipper()) {
m_c->object_clipper()->set_position(0, true);
}
// we are actually shutting down
on_shutdown();
m_old_mo_id = -1;
//m_iva.release_geometry();
m_triangle_selectors.clear();
//Camera& camera = wxGetApp().plater()->get_camera();
//camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ());
//camera.set_target(m_previous_target);
//camera.recover_from_free_camera();
}
m_old_state = m_state;
m_vertical_only = false;
m_horizontal_only = false;
m_is_front_view = false;
m_front_view_radian = 0;
}
void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&)
{
// We should update the gizmo from current ModelObject, but it is not
// possible at this point. That would require having updated selection and
// common gizmos data, which is not done at this point. Instead, save
// a flag to do the update in set_painter_gizmo_data, which will be called
// soon after.
m_schedule_update = true;
}
TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const {
const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane();
if (clipping_plane == nullptr || !clipping_plane->is_active())
return {};
const Vec3d clp_normal = clipping_plane->get_normal();
const double clp_offset = clipping_plane->get_offset();
const Transform3d trafo_normal = Transform3d(trafo.linear().transpose());
const Transform3d trafo_inv = trafo.inverse();
Vec3d point_on_plane = clp_normal * clp_offset;
Vec3d point_on_plane_transformed = trafo_inv * point_on_plane;
Vec3d normal_transformed = trafo_normal * clp_normal;
auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed));
return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed});
}
void GLGizmoPainterBase::change_camera_view_angle(float front_view_radian)
{
wxGetApp().plater()->get_camera().select_view("front");
const Selection &selection = m_parent.get_selection();
auto rotate_target = selection.get_bounding_box().center();
wxGetApp().plater()->get_camera().rotate_local_with_target(Vec3d(0, front_view_radian, 0), rotate_target);
}
std::array<float, 4> TriangleSelectorGUI::get_seed_fill_color(const std::array<float, 4> &base_color)
{
// BBS
return {
base_color[0] * 1.25f < 1.f ? base_color[0] * 1.25f : 1.f,
base_color[1] * 1.25f < 1.f ? base_color[1] * 1.25f : 1.f,
base_color[2] * 1.25f < 1.f ? base_color[2] * 1.25f : 1.f,
1.f};
}
void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
{
if (m_update_render_data) {
update_render_data();
m_update_render_data = false;
}
auto* shader = wxGetApp().get_current_shader();
if (! shader)
return;
assert(shader->get_name() == "gouraud");
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);});
shader->set_uniform("offset_depth_buffer", true);
for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color),
std::make_pair(&m_iva_blockers, blockers_color)}) {
if (iva.first->has_VBOs()) {
shader->set_uniform("uniform_color", iva.second);
iva.first->render();
}
}
for (auto &iva : m_iva_seed_fills)
if (iva.has_VBOs()) {
size_t color_idx = &iva - &m_iva_seed_fills.front();
const std::array<float, 4> &color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
color_idx == 2 ? blockers_color :
GLVolume::NEUTRAL_COLOR);
shader->set_uniform("uniform_color", color);
iva.render();
}
if (m_paint_contour.has_VBO()) {
ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
shader->stop_using();
auto *contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
m_paint_contour.render();
glsafe(::glDepthFunc(GL_LESS));
contour_shader->stop_using();
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
if (imgui)
render_debug(imgui);
else
assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
#endif
}
void TriangleSelectorGUI::update_render_data()
{
int enf_cnt = 0;
int blc_cnt = 0;
std::vector<int> seed_fill_cnt(m_iva_seed_fills.size(), 0);
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->release_geometry();
for (auto &iva : m_iva_seed_fills)
iva.release_geometry();
for (const Triangle &tr : m_triangles) {
bool is_valid = tr.valid();
bool is_split = tr.is_split();
EnforcerBlockerType type = tr.get_state();
bool is_select_by_seed_fill = tr.is_selected_by_seed_fill();
if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill()))
continue;
int tr_state = int(tr.get_state());
GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] :
tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers :
m_iva_blockers;
int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] :
tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt :
blc_cnt;
const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v;
const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v;
const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v;
//FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort
// or the current implementation may be more cache friendly.
const Vec3f n = (v1 - v0).cross(v2 - v1).normalized();
iva.push_geometry(v0, n);
iva.push_geometry(v1, n);
iva.push_geometry(v2, n);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
}
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->finalize_geometry(true);
for (auto &iva : m_iva_seed_fills)
iva.finalize_geometry(true);
m_paint_contour.release_geometry();
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
for (const Vec2i &edge : contour_edges) {
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
}
m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0);
std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0);
m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
m_paint_contour.finalize_geometry();
}
// BBS
bool TrianglePatch::is_fragment() const
{
return this->area < TriangleSelectorPatch::gap_area;
}
float TriangleSelectorPatch::gap_area = TriangleSelectorPatch::GapAreaMin;
void TriangleSelectorPatch::render(ImGuiWrapper* imgui)
{
static bool last_show_wireframe = false;
if (last_show_wireframe != wxGetApp().plater()->is_show_wireframe()) {
last_show_wireframe = wxGetApp().plater()->is_show_wireframe();
m_update_render_data = true;
m_paint_changed = true;
}
if (m_update_render_data)
update_render_data();
auto* shader = wxGetApp().get_current_shader();
if (!shader)
return;
assert(shader->get_name() == "mm_gouraud");
GLint position_id = -1;
GLint barycentric_id = -1;
bool show_wireframe = false;
if (wxGetApp().plater()->is_wireframe_enabled()) {
if (m_need_wireframe && wxGetApp().plater()->is_show_wireframe()) {
position_id = shader->get_attrib_location("v_position");
barycentric_id = shader->get_attrib_location("v_barycentric");
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", show_wireframe on");
shader->set_uniform("show_wireframe", true);
show_wireframe = true;
}
else {
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", show_wireframe off");
shader->set_uniform("show_wireframe", false);
}
}
for (size_t buffer_idx = 0; buffer_idx < m_triangle_patches.size(); ++buffer_idx) {
if (this->has_VBOs(buffer_idx)) {
const TrianglePatch& patch = m_triangle_patches[buffer_idx];
std::array<float, 4> color;
if (patch.is_fragment() && !patch.neighbor_types.empty()) {
size_t color_idx = (size_t)*patch.neighbor_types.begin();
color = m_ebt_colors[color_idx];
color[3] = 0.85;
}
else {
size_t color_idx = (size_t)patch.type;
color = m_ebt_colors[color_idx];
}
//to make black not too hard too see
std::array<float, 4> new_color = adjust_color_for_rendering(color);
shader->set_uniform("uniform_color", new_color);
//shader->set_uniform("uniform_color", color);
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", buffer_idx %1%: new_color[%2%, %3%, %4%, %5%]")%buffer_idx%new_color[0]%new_color[1]%new_color[2]%new_color[3];
this->render(buffer_idx, (int) position_id, show_wireframe);
}
}
if (m_paint_contour.has_VBO())
{
ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); });
shader->stop_using();
auto* contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
m_paint_contour.render();
glsafe(::glDepthFunc(GL_LESS));
contour_shader->stop_using();
}
m_update_render_data = false;
}
void TriangleSelectorPatch::update_triangles_per_type()
{
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", enter");
m_triangle_patches.resize((int)EnforcerBlockerType::ExtruderMax + 1);
for (int i = 0; i < m_triangle_patches.size(); i++) {
auto& patch = m_triangle_patches[i];
patch.type = (EnforcerBlockerType)i;
patch.triangle_indices.reserve(m_triangles.size() / 3);
}
bool using_wireframe = (wxGetApp().plater()->is_wireframe_enabled() && wxGetApp().plater()->is_show_wireframe()) ? true : false;
for (auto& triangle : m_triangles) {
if (!triangle.valid() || triangle.is_split())
continue;
int state = (int)triangle.get_state();
auto& patch = m_triangle_patches[state];
//patch.triangle_indices.insert(patch.triangle_indices.end(), triangle.verts_idxs.begin(), triangle.verts_idxs.end());
for (int i = 0; i < 3; ++i) {
int j = triangle.verts_idxs[i];
int index = using_wireframe?int(patch.patch_vertices.size()/6) : int(patch.patch_vertices.size()/3);
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: i=%2%, j=%3%, index=%4%, v[%5%,%6%,%7%]")%__LINE__%i%j%index%m_vertices[j].v(0)%m_vertices[j].v(1)%m_vertices[j].v(2);
patch.patch_vertices.emplace_back(m_vertices[j].v(0));
patch.patch_vertices.emplace_back(m_vertices[j].v(1));
patch.patch_vertices.emplace_back(m_vertices[j].v(2));
if (using_wireframe) {
if (i == 0) {
patch.patch_vertices.emplace_back(1.0);
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(0.0);
}
else if (i == 1) {
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(1.0);
patch.patch_vertices.emplace_back(0.0);
}
else {
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(1.0);
}
}
patch.triangle_indices.emplace_back( index);
}
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: state=%2%, vertice size=%3%, triangle size %4%")%__LINE__%state%patch.patch_vertices.size()%patch.triangle_indices.size();
}
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("exit");
}
void TriangleSelectorPatch::update_selector_triangles()
{
for (TrianglePatch& patch : m_triangle_patches) {
if (!patch.is_fragment() || patch.neighbor_types.empty())
continue;
EnforcerBlockerType type = *patch.neighbor_types.begin();
for (int facet_idx : patch.facet_indices) {
m_triangles[facet_idx].set_state(type);
}
}
}
void TriangleSelectorPatch::update_triangles_per_patch()
{
auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors();
std::vector<bool> visited(m_triangles.size(), false);
bool using_wireframe = (wxGetApp().plater()->is_wireframe_enabled() && wxGetApp().plater()->is_show_wireframe()) ? true : false;
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i& neighbors, const Vec3i& neighbors_propagated) -> std::vector<int> {
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
std::vector<int> touching_triangles;
Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] };
append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles);
append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles);
append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles);
for (int neighbor_idx : neighbors_propagated)
if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split())
touching_triangles.emplace_back(neighbor_idx);
return touching_triangles;
};
auto calc_fragment_area = [this](const TrianglePatch& patch, float max_limit_area, int stride) {
double total_area = 0.f;
const std::vector<int>& ti = patch.triangle_indices;
/*for (int i = 0; i < ti.size() / 3; i++) {
total_area += std::abs((m_vertices[ti[i]].v - m_vertices[ti[i + 1]].v)
.cross(m_vertices[ti[i]].v - m_vertices[ti[i + 2]].v).norm()) / 2;
if (total_area >= max_limit_area)
break;
}*/
const std::vector<float>& vertices = patch.patch_vertices;
for (int i = 0; i < ti.size(); i+=3) {
stl_vertex v0(vertices[ti[i] * stride], vertices[ti[i] * stride + 1], vertices[ti[i] * stride + 2]);
stl_vertex v1(vertices[ti[i + 1] * stride], vertices[ti[i + 1] * stride + 1], vertices[ti[i + 1] * stride + 2]);
stl_vertex v2(vertices[ti[i + 2] * stride], vertices[ti[i + 2] * stride + 1], vertices[ti[i + 2] * stride + 2]);
total_area += std::abs((v0 - v1).cross(v0 - v2).norm()) / 2;
if (total_area >= max_limit_area)
break;
}
return total_area;
};
int start_facet_idx = 0;
while (1) {
for (; start_facet_idx < visited.size(); start_facet_idx++) {
if (!visited[start_facet_idx] && m_triangles[start_facet_idx].valid() && !m_triangles[start_facet_idx].is_split())
break;
}
if (start_facet_idx >= m_triangles.size())
break;
EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state();
TrianglePatch patch;
std::queue<int> facet_queue;
facet_queue.push(start_facet_idx);
while (!facet_queue.empty()) {
int current_facet = facet_queue.front();
facet_queue.pop();
assert(!m_triangles[current_facet].is_split());
if (!visited[current_facet]) {
Triangle& triangle = m_triangles[current_facet];
for (int i = 0; i < 3; ++i) {
int j = triangle.verts_idxs[i];
int index = using_wireframe?int(patch.patch_vertices.size()/6) : int(patch.patch_vertices.size()/3);
patch.patch_vertices.emplace_back(m_vertices[j].v(0));
patch.patch_vertices.emplace_back(m_vertices[j].v(1));
patch.patch_vertices.emplace_back(m_vertices[j].v(2));
if (using_wireframe) {
if (i == 0) {
patch.patch_vertices.emplace_back(1.0);
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(0.0);
}
else if (i == 1) {
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(1.0);
patch.patch_vertices.emplace_back(0.0);
}
else {
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(0.0);
patch.patch_vertices.emplace_back(1.0);
}
}
patch.triangle_indices.emplace_back( index);
}
//patch.triangle_indices.insert(patch.triangle_indices.end(), triangle.verts_idxs.begin(), triangle.verts_idxs.end());
patch.facet_indices.push_back(current_facet);
std::vector<int> touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
for (const int tr_idx : touching_triangles) {
if (tr_idx < 0)
continue;
if (m_triangles[tr_idx].get_state() != start_facet_state) {
patch.neighbor_types.insert(m_triangles[tr_idx].get_state());
continue;
}
// should check visited state after color for neight types
if (visited[tr_idx])
continue;
assert(!m_triangles[tr_idx].is_split());
facet_queue.push(tr_idx);
}
}
visited[current_facet] = true;
}
patch.area = calc_fragment_area(patch, GapAreaMax, using_wireframe?6:3);
patch.type = start_facet_state;
m_triangle_patches.emplace_back(std::move(patch));
}
}
void TriangleSelectorPatch::set_filter_state(bool is_filter_state)
{
if (!m_filter_state && is_filter_state) {
m_filter_state = is_filter_state;
this->release_geometry();
update_render_data();
}
m_filter_state = is_filter_state;
}
void TriangleSelectorPatch::update_render_data()
{
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", m_paint_changed=%1%, m_triangle_patches.size %2%")%m_paint_changed%m_triangle_patches.size();
if (m_paint_changed || (m_triangle_patches.size() == 0)) {
this->release_geometry();
/*m_patch_vertices.reserve(m_vertices.size() * 3);
for (const Vertex& vr : m_vertices) {
m_patch_vertices.emplace_back(vr.v.x());
m_patch_vertices.emplace_back(vr.v.y());
m_patch_vertices.emplace_back(vr.v.z());
}
this->finalize_vertices();*/
if (m_filter_state)
update_triangles_per_patch();
else
update_triangles_per_type();
this->finalize_triangle_indices();
m_paint_changed = false;
}
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", before paint_contour");
m_paint_contour.release_geometry();
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
for (const Vec2i& edge : contour_edges) {
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
}
m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0);
std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0);
m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
m_paint_contour.finalize_geometry();
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", exit");
}
void TriangleSelectorPatch::render(int triangle_indices_idx, int position_id, bool show_wireframe)
{
assert(triangle_indices_idx < this->m_triangle_indices_VBO_ids.size());
assert(this->m_triangle_patches.size() == this->m_triangle_indices_VBO_ids.size());
//assert(this->m_vertices_VBO_id != 0);
assert(this->m_triangle_patches.size() == this->m_vertices_VBO_ids.size());
assert(this->m_vertices_VBO_ids[triangle_indices_idx] != 0);
assert(this->m_triangle_indices_VBO_ids[triangle_indices_idx] != 0);
//glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_id));
//glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) {
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_ids[triangle_indices_idx]));
if (show_wireframe) {
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void *) 0));
glsafe(::glColorPointer(3,GL_FLOAT, 6 * sizeof(float), (const void *) (3 * sizeof(float))));
glsafe(::glEnableClientState(GL_COLOR_ARRAY));
}
else {
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
}
//glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: triangle_indices_idx %2%, bind vertex vbo, buffer id %3%")%__LINE__%triangle_indices_idx%this->m_vertices_VBO_ids[triangle_indices_idx];
}
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_triangle_indices_VBO_ids[triangle_indices_idx]));
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->m_triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: triangle_indices_idx %2%, bind indices vbo, buffer id %3%")%__LINE__%triangle_indices_idx%this->m_triangle_indices_VBO_ids[triangle_indices_idx];
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&(position_id != -1))
glsafe(::glDisableVertexAttribArray(position_id));
if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&show_wireframe) {
glsafe(::glEnableClientState(GL_COLOR_ARRAY));
}
if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0)
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void TriangleSelectorPatch::release_geometry()
{
/*if (m_vertices_VBO_id) {
glsafe(::glDeleteBuffers(1, &m_vertices_VBO_id));
m_vertices_VBO_id = 0;
}*/
for (auto& vertice_VBO_id : m_vertices_VBO_ids) {
glsafe(::glDeleteBuffers(1, &vertice_VBO_id));
vertice_VBO_id = 0;
}
for (auto& triangle_indices_VBO_id : m_triangle_indices_VBO_ids) {
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
triangle_indices_VBO_id = 0;
}
this->clear();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: released geometry")%__LINE__;
}
void TriangleSelectorPatch::finalize_vertices()
{
/*assert(m_vertices_VBO_id == 0);
if (!this->m_patch_vertices.empty()) {
glsafe(::glGenBuffers(1, &this->m_vertices_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->m_patch_vertices.size() * sizeof(float), this->m_patch_vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->m_patch_vertices.clear();
}*/
}
void TriangleSelectorPatch::finalize_triangle_indices()
{
m_vertices_VBO_ids.resize(m_triangle_patches.size());
m_triangle_indices_VBO_ids.resize(m_triangle_patches.size());
m_triangle_indices_sizes.resize(m_triangle_patches.size());
assert(std::all_of(m_triangle_indices_VBO_ids.cbegin(), m_triangle_indices_VBO_ids.cend(), [](const auto& ti_VBO_id) { return ti_VBO_id == 0; }));
for (size_t buffer_idx = 0; buffer_idx < m_triangle_patches.size(); ++buffer_idx) {
std::vector<float>& patch_vertices = m_triangle_patches[buffer_idx].patch_vertices;
if (!patch_vertices.empty()) {
glsafe(::glGenBuffers(1, &m_vertices_VBO_ids[buffer_idx]));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices_VBO_ids[buffer_idx]));
glsafe(::glBufferData(GL_ARRAY_BUFFER, patch_vertices.size() * sizeof(float), patch_vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: buffer_idx %2%, vertices size %3%, buffer id %4%")%__LINE__%buffer_idx%patch_vertices.size()%m_vertices_VBO_ids[buffer_idx];
patch_vertices.clear();
}
std::vector<int>& triangle_indices = m_triangle_patches[buffer_idx].triangle_indices;
m_triangle_indices_sizes[buffer_idx] = triangle_indices.size();
if (!triangle_indices.empty()) {
glsafe(::glGenBuffers(1, &m_triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangle_indices.size() * sizeof(int), triangle_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: buffer_idx %2%, vertices size %3%, buffer id %4%")%__LINE__%buffer_idx%triangle_indices.size()%m_triangle_indices_VBO_ids[buffer_idx];
triangle_indices.clear();
}
}
}
void GLPaintContour::render() const
{
assert(this->m_contour_VBO_id != 0);
assert(this->m_contour_EBO_id != 0);
glsafe(::glLineWidth(4.0f));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (this->contour_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void GLPaintContour::finalize_geometry()
{
assert(this->m_contour_VBO_id == 0);
assert(this->m_contour_EBO_id == 0);
if (!this->contour_vertices.empty()) {
glsafe(::glGenBuffers(1, &this->m_contour_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->contour_vertices.clear();
}
if (!this->contour_indices.empty()) {
glsafe(::glGenBuffers(1, &this->m_contour_EBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->contour_indices.clear();
}
}
void GLPaintContour::release_geometry()
{
if (this->m_contour_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id));
this->m_contour_VBO_id = 0;
}
if (this->m_contour_EBO_id) {
glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id));
this->m_contour_EBO_id = 0;
}
this->clear();
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
{
imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
static float edge_limit = 1.f;
imgui->text("Edge limit (mm): ");
imgui->slider_float("", &edge_limit, 0.1f, 8.f);
set_edge_limit(edge_limit);
imgui->checkbox("Show split triangles: ", m_show_triangles);
imgui->checkbox("Show invalid triangles: ", m_show_invalid);
int valid_triangles = m_triangles.size() - m_invalid_triangles;
imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
"/" + std::to_string(m_triangles.size()));
imgui->text("Vertices: " + std::to_string(m_vertices.size()));
if (imgui->button("Force garbage collection"))
garbage_collect();
if (imgui->button("Serialize - deserialize")) {
auto map = serialize();
deserialize(map);
}
imgui->end();
if (! m_show_triangles)
return;
enum vtype {
ORIGINAL = 0,
SPLIT,
INVALID
};
for (auto& va : m_varrays)
va.release_geometry();
std::array<int, 3> cnts;
::glScalef(1.01f, 1.01f, 1.01f);
for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
const Triangle& tr = m_triangles[tr_id];
GLIndexedVertexArray* va = nullptr;
int* cnt = nullptr;
if (tr_id < m_orig_size_indices) {
va = &m_varrays[ORIGINAL];
cnt = &cnts[ORIGINAL];
}
else if (tr.valid()) {
va = &m_varrays[SPLIT];
cnt = &cnts[SPLIT];
}
else {
if (! m_show_invalid)
continue;
va = &m_varrays[INVALID];
cnt = &cnts[INVALID];
}
for (int i=0; i<3; ++i)
va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va->push_triangle(*cnt,
*cnt+1,
*cnt+2);
*cnt += 3;
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
GLIndexedVertexArray& va = m_varrays[i];
va.finalize_geometry(true);
if (va.has_VBOs()) {
switch (i) {
case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
}
va.render();
}
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
#endif
} // namespace Slic3r::GUI