diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 062dfc19c7..96a09d175f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -247,6 +247,8 @@ set(SLIC3R_GUI_SOURCES Utils/Process.cpp Utils/Process.hpp Utils/Profile.hpp + Utils/RaycastManager.cpp + Utils/RaycastManager.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp Utils/HexFile.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8bf483e7f2..2389ef76d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -36,6 +36,8 @@ // uncomment for easier debug //#define ALLOW_DEBUG_MODE + + using namespace Slic3r; using namespace Slic3r::GUI; @@ -168,61 +170,58 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) { // filter events - if (!mouse_event.Dragging() && !mouse_event.LeftUp()) return false; + if (!mouse_event.Dragging() && + !mouse_event.LeftUp() && + !mouse_event.LeftDown()) + return false; - Selection& selection = m_parent.get_selection(); - if (!selection.is_single_volume()) return false; + // text volume must be selected + if (m_volume == nullptr) return false; - bool drag_text = selection.is_dragging(); - if (!drag_text) return false; + // must exist hover object + int hovered_id = m_parent.get_first_hover_volume_idx(); + if (hovered_id < 0) return false; + + GLVolume *gl_volume = m_parent.get_volumes().volumes[hovered_id]; + const ModelObjectPtrs &objects = wxGetApp().plater()->model().objects; + ModelVolume *act_model_volume = get_model_volume(gl_volume, objects); + + // hovered object must be actual text volume + if (m_volume != act_model_volume) return false; + + RaycastManager::SkipVolume skip(m_volume->id().id); + // detect start text dragging + if (mouse_event.LeftDown()) { + // initialize raycasters + // TODO: move to job, for big scene it slow down + m_raycast_manager.actualize(objects, &skip); + return false; + } // wxCoord == int --> wx/types.h Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); Vec2d mouse_pos = mouse_coord.cast(); - - - //auto trmat2 = transform_on_surface(mouse_pos); - //if (!trmat2.has_value()) return false; - - int hovered_id = m_parent.get_first_hover_volume_idx(); - if (hovered_id < 0) return false; - - GLVolume *gl_volume = m_parent.get_volumes().volumes[hovered_id]; - auto & objects = wxGetApp().plater()->model().objects; - ModelVolume *act_model_volume = get_model_volume(gl_volume, objects); - - static ModelVolume *model_volume = nullptr; - static std::unique_ptr mrc; - if (act_model_volume == nullptr) { - mrc = nullptr; - return false; + auto hit = m_raycast_manager.unproject(mouse_pos, &skip); + if (!hit.has_value()) { + // there is no hit + m_parent.toggle_model_objects_visibility(true); + m_temp_transformation = {}; + return false; } - if (model_volume != act_model_volume) { - // hovered different object create different raycaster - model_volume = act_model_volume; - mrc = std::make_unique(model_volume->mesh()); - } - - Transform3d trafo = gl_volume->world_matrix(); - const Camera &camera = wxGetApp().plater()->get_camera(); - - Vec3f position = Vec3f::Zero(); - Vec3f normal = Vec3f::UnitZ(); - if (!mrc->unproject_on_mesh(mouse_pos, trafo, camera, position, normal)) - return false; - - Transform3d trmat = get_emboss_transformation(position, normal); - // TODO: store z-rotation and aply after transformation matrix - if (mouse_event.Dragging()) { - // create temporary position - //selection.translate(Vec3d(), ECoordinatesType::Local); - //selection.get_insvolume(0); - return true; - } else if (mouse_event.LeftUp()) { - - + // Show temporary position + m_parent.toggle_model_objects_visibility(false, m_volume->get_object(), gl_volume->instance_idx(), m_volume); + + // TODO: store z-rotation and aply after transformation matrix + Transform3d object_trmat = m_raycast_manager.get_transformation(hit->tr_key); + RaycastManager::SurfacePoint sp = *hit; + Transform3d trmat = get_emboss_transformation(sp.position, sp.normal); + m_temp_transformation = object_trmat * trmat; + } else if (mouse_event.LeftUp()) { + // Apply temporary position + m_parent.toggle_model_objects_visibility(true); + m_temp_transformation = {}; } return false; } @@ -257,21 +256,25 @@ void GLGizmoEmboss::on_render() { if (m_volume == nullptr) return; glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + if (m_temp_transformation.has_value()) { + // draw text volume on temporary position + const Selection &selection = m_parent.get_selection(); + const GLVolume& gl_volume = *selection.get_volume(*selection.get_volume_idxs().begin()); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_temp_transformation->data())); + GLShaderProgram *shader = wxGetApp().get_shader("gouraud_light"); + shader->start_using(); + // dragging object must be selected so draw it with selected color + shader->set_uniform("uniform_color", GLVolume::SELECTED_COLOR); + gl_volume.indexed_vertex_array.render(); + shader->stop_using(); + glsafe(::glPopMatrix()); + } - m_rotate_gizmo.render(); - - if (!m_preview.is_initialized()) return; - - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(m_preview_trmat.data())); - auto *contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - glsafe(::glLineWidth(1.0f)); - glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); - m_preview.render(); - glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); - contour_shader->stop_using(); - glsafe(::glPopMatrix()); + // Do NOT render rotation when dragging + if (!m_parent.is_dragging() || m_dragging) + m_rotate_gizmo.render(); } void GLGizmoEmboss::on_render_for_picking() { @@ -510,7 +513,7 @@ ModelVolume *GLGizmoEmboss::get_selected_volume() } ModelVolume *GLGizmoEmboss::get_model_volume(const GLVolume * gl_volume, - const ModelObjectPtrs objects) + const ModelObjectPtrs& objects) { const GLVolume::CompositeID &id = gl_volume->composite_id; @@ -526,7 +529,7 @@ ModelVolume *GLGizmoEmboss::get_model_volume(const GLVolume * gl_volume, } ModelVolume *GLGizmoEmboss::get_selected_volume(const Selection &selection, - const ModelObjectPtrs objects) + const ModelObjectPtrs& objects) { int object_idx = selection.get_object_idx(); // is more object selected? @@ -609,19 +612,6 @@ void GLGizmoEmboss::draw_window() } } m_imgui->disabled_end(); - - ImGui::SameLine(); - const Selection &s = m_parent.get_selection(); - bool dragging = s.is_dragging(); - ImGui::Checkbox("dragging", &dragging); - static bool change_position = true; - ImGui::SameLine(); - ImGui::Checkbox("position", &change_position); - if (change_position) { - // draw text on coordinate of mouse - preview_positon(); - } - } Transform3d GLGizmoEmboss::get_emboss_transformation(const Vec3f &position, @@ -710,127 +700,6 @@ std::optional GLGizmoEmboss::transform_on_surface( return get_emboss_transformation(closest->position, closest->normal); } -void GLGizmoEmboss::preview_positon() { - Vec2d mouse_pos = m_parent.get_local_mouse_position(); - - - auto rc = m_c->raycaster(); - //auto rc2 = rc->raycaster(); - - static std::unique_ptr mrc; - if (m_volume == nullptr) return; - - // TODO: select hovered volume - static ModelVolume *volume = nullptr; - ModelVolume *prev_volume = volume; - volume = nullptr; - for (ModelVolume *mv : m_volume->get_object()->volumes) { - if (!mv->is_model_part()) continue; - if (mv->text_configuration.has_value()) continue; - volume = mv; - break; - } - - if (prev_volume != nullptr && prev_volume != volume) { - // change volume - mrc = nullptr; - } - - // no volume for raycast - if (volume == nullptr) return; - - if (mrc == nullptr) { - mrc = std::make_unique(volume->mesh()); - } - - // find glvolume for transformation - const GLVolume *gl_volume = nullptr; - for (const GLVolume *v : m_parent.get_volumes().volumes) { - auto &cid = v->composite_id; - ObjectID oid = m_parent.get_model()->objects[cid.object_id]->volumes[cid.volume_id]->id(); - if (oid != volume->id()) continue; - gl_volume = v; - } - if (gl_volume == nullptr) return; - - Transform3d trafo = gl_volume->world_matrix(); - const Camera &camera = wxGetApp().plater()->get_camera(); - - Vec3f position; - Vec3f normal; - size_t face_id; - if (mrc->unproject_on_mesh(mouse_pos, trafo, camera, position, normal, nullptr, &face_id)) { - // draw triangle - auto &its =volume->mesh().its; - auto &triangle = its.indices[face_id]; - Points pts; - for (const auto &t : triangle) { - auto &v = its.vertices[t]; - Points p = CameraUtils::project(camera, - {trafo * v.cast()}); - pts.push_back(p.front()); - } - ImGuiWrapper::draw(Polygon(pts)); - - m_preview.init_from(m_volume->mesh().its); - - // up and emboss direction for generated model - Vec3d text_up = Vec3d::UnitY(); - Vec3d text_view = Vec3d::UnitZ(); - - // wanted up direction of result - Vec3d normal_up_dir = Vec3d::UnitZ(); - if (abs(normal.z()) > 0.9) normal_up_dir = Vec3d::UnitY(); - - Vec3d normal3d = normal.cast(); - normal3d.normalize(); // after cast from float it needs to be normalized again - - // create perpendicular unit vector to surface triangle normal vector - // lay on surface of triangle and define up vector for text - Vec3d normal_up = normal3d.cross(normal_up_dir).cross(normal3d); - normal_up.normalize(); // normal3d is NOT perpendicular to normal_up_dir - - // perpendicular to emboss vector of text and normal - Vec3d axis_view = text_view.cross(normal3d); - double angle_view = std::acos(text_view.dot(normal3d)); // in rad - axis_view.normalize(); - Eigen::AngleAxis view_rot(angle_view, axis_view); - Vec3d normal_up_rot = view_rot.matrix().inverse() * normal_up; - normal_up_rot.normalize(); - double angle_up = std::acos(text_up.dot(normal_up_rot)); - - // text_view and text_view2 should have same direction - Vec3d text_view2 = text_up.cross(normal_up_rot); - Vec3d diff_view = text_view - text_view2; - if (std::fabs(diff_view.x()) > 1. || std::fabs(diff_view.y()) > 1. || - std::fabs(diff_view.z()) > 1.) // oposit direction - angle_up *= -1.; - - Eigen::AngleAxis up_rot(angle_up, text_view); - - Transform3d transform = Transform3d::Identity(); - transform.translate(position.cast()); - transform.rotate(view_rot); - transform.rotate(up_rot); - - transform = get_emboss_transformation(position, normal); - - //Transform3d rot = Transform3d::Identity(); - //rot.rotate(Eigen::AngleAxis(angle, axis)); - - m_preview_trmat = trafo * transform; - } else { - m_preview.reset(); - } - - - // draw mouse position - Point mouse_point = mouse_pos.cast(); - Slic3r::Polygon mouse_triangle({mouse_point, mouse_point + Point(55, 0), - mouse_point + Point(0, 55)}); - ImGuiWrapper::draw(mouse_triangle); -} - void GLGizmoEmboss::draw_font_list() { const float & max_width = m_gui_cfg->max_font_name_width; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index bd32767ee9..3b72f09d10 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -6,6 +6,7 @@ #include "GLGizmoBase.hpp" #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLTexture.hpp" +#include "slic3r/Utils/RaycastManager.hpp" #include "admesh/stl.h" // indexed_triangle_set #include @@ -69,13 +70,12 @@ private: void check_selection(); // more general function --> move to select ModelVolume *get_selected_volume(); - static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs objects); - static ModelVolume *get_selected_volume(const Selection &selection, const ModelObjectPtrs objects); + static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs& objects); + static ModelVolume *get_selected_volume(const Selection &selection, const ModelObjectPtrs& objects); // create volume from text - main functionality bool process(); void close(); void draw_window(); - void preview_positon(); void draw_font_list(); void draw_text_input(); void draw_advanced(); @@ -159,9 +159,10 @@ private: // Rotation gizmo GLGizmoRotate m_rotate_gizmo; - // preview position - GLModel m_preview; - Transform3d m_preview_trmat; + // TODO: it should be accessible by other gizmo too. + // May be move to plater? + RaycastManager m_raycast_manager; + std::optional m_temp_transformation; // initialize when GL is accessible bool m_is_initialized; diff --git a/src/slic3r/Utils/RaycastManager.cpp b/src/slic3r/Utils/RaycastManager.cpp new file mode 100644 index 0000000000..715a8c47c6 --- /dev/null +++ b/src/slic3r/Utils/RaycastManager.cpp @@ -0,0 +1,122 @@ +#include "RaycastManager.hpp" + +// include for earn camera +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Camera.hpp" + +using namespace Slic3r::GUI; + +void RaycastManager::actualize(const ModelObjectPtrs &objects, + const ISkip * skip) +{ + // check if volume was removed + std::set removed_casters; + for (const auto &raycaster_item : raycasters) + removed_casters.insert(raycaster_item.first); + + // check if inscance was removed + std::set removed_transformation; + for (const auto &item : transformations) + removed_transformation.insert(item.first); + + for (const ModelObject *object : objects) { + // actualize MeshRaycaster + for (const ModelVolume *volume : object->volumes) { + size_t oid = volume->id().id; + if (skip != nullptr && skip->skip(oid)) { + removed_casters.erase(oid); + continue; + } + auto item = raycasters.find(oid); + if (item != raycasters.end()) { + removed_casters.erase(oid); + // alredy in list only actualize + // TODO: check triangles when change than actualize MeshRaycaster + } else { + // add new raycaster + auto raycaster = std::make_unique( + volume->mesh()); + raycasters.insert(std::make_pair(oid, std::move(raycaster))); + } + } + + // actualize transformation matrices + for (const ModelVolume *volume : object->volumes) { + if (skip != nullptr && skip->skip(volume->id().id)) continue; + const Transform3d &volume_tr = volume->get_matrix(); + for (const ModelInstance *instance : object->instances) { + const Transform3d& instrance_tr = instance->get_matrix(); + Transform3d transformation = instrance_tr * volume_tr; + // TODO: add SLA shift Z + // transformation.translation()(2) += m_sla_shift_z; + + TrKey tr_key = std::make_pair(instance->id().id, volume->id().id); + auto item = transformations.find(tr_key); + if (item != transformations.end()) { + // actualize transformation all the time + item->second = transformation; + removed_transformation.erase(tr_key); + } else { + // add new transformation + transformations.insert( + std::make_pair(tr_key, transformation)); + } + } + } + } + + // remove non existing volumes + for (size_t volume_oid : removed_casters) raycasters.erase(volume_oid); + // remove non existing transformations + for (const TrKey& transformation_key : removed_transformation) + transformations.erase(transformation_key); +} + +std::optional RaycastManager::unproject( + const Vec2d &mouse_pos, const ISkip *skip) const +{ + struct HitWithDistance: public Hit + { + double squared_distance; + HitWithDistance(double squared_distance, + const TrKey & key, + const SurfacePoint &surface_point) + : Hit(key, surface_point.position, surface_point.normal) + , squared_distance(squared_distance) + {} + }; + std::optional closest; + + const Camera &camera = wxGetApp().plater()->get_camera(); + for (const auto &item : transformations) { + const TrKey &key = item.first; + size_t volume_id = key.second; + if (skip != nullptr && skip->skip(volume_id)) continue; + const Transform3d &transformation = item.second; + auto raycaster_it = raycasters.find(volume_id); + if (raycaster_it == raycasters.end()) continue; + const MeshRaycaster &raycaster = *(raycaster_it->second); + SurfacePoint surface_point; + bool success = raycaster.unproject_on_mesh( + mouse_pos, transformation, camera, + surface_point.position, surface_point.normal); + if (!success) continue; + + Vec3d act_hit_tr = transformation * surface_point.position.cast(); + double squared_distance = (camera.get_position() - act_hit_tr).squaredNorm(); + if (closest.has_value() && + closest->squared_distance < squared_distance) + continue; + closest = HitWithDistance(squared_distance, key, surface_point); + } + + //if (!closest.has_value()) return {}; + return closest; +} + +Slic3r::Transform3d RaycastManager::get_transformation(const TrKey &tr_key) const { + auto item = transformations.find(tr_key); + if (item == transformations.end()) return Transform3d::Identity(); + return item->second; +} \ No newline at end of file diff --git a/src/slic3r/Utils/RaycastManager.hpp b/src/slic3r/Utils/RaycastManager.hpp new file mode 100644 index 0000000000..9dc0514c7b --- /dev/null +++ b/src/slic3r/Utils/RaycastManager.hpp @@ -0,0 +1,103 @@ +#ifndef slic3r_RaycastManager_hpp_ +#define slic3r_RaycastManager_hpp_ + +#include // unique_ptr +#include // unique_ptr +#include +#include "slic3r/GUI/MeshUtils.hpp" // MeshRaycaster +#include "libslic3r/Point.hpp" // Transform3d +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Model.hpp" // ModelObjectPtrs, ModelObject, ModelInstance, ModelVolume + +namespace Slic3r::GUI{ + +class RaycastManager +{ + // ModelVolume + std::map> raycasters; + + // Key for transformation consist of unique volume and instance + // ModelInstance, ModelVolume + using TrKey = std::pair; + std::map transformations; + + // should contain shared pointer to camera but it is not shared pointer so it need it every time when casts rays + +public: + class ISkip{ + public: + virtual ~ISkip() = default; + /// + /// Condition to not process specific transformation + /// + /// Transformation key + /// True on skip otherwise false + //virtual bool skip(const TrKey &key) const { return false; } + + /// + /// Condition to not process model volume + /// + /// ObjectID of model volume to not process + /// True on skip otherwise false + virtual bool skip(const size_t &model_volume_id) const { return false; } + }; + + /// + /// Actualize raycasters + transformation + /// Detection of removed object + /// Detection of removed instance + /// Detection of removed volume + /// + /// Condifiton for skip actualization + /// Model representation + void actualize(const ModelObjectPtrs &objects, + const ISkip * skip = nullptr); + + // TODO: it is more general object move outside of this class + struct SurfacePoint + { + Vec3f position = Vec3f::Zero(); + Vec3f normal = Vec3f::UnitZ(); + SurfacePoint() = default; + SurfacePoint(Vec3f position, Vec3f normal) + : position(position), normal(normal) + {} + }; + + struct Hit: public SurfacePoint + { + TrKey tr_key; + Hit(TrKey tr_key, Vec3f position, Vec3f normal) + : SurfacePoint(position, normal), tr_key(tr_key) + {} + }; + + class SkipVolume: public ISkip + { + size_t volume_id; + public: + SkipVolume(size_t volume_id) : volume_id(volume_id) {} + bool skip(const size_t &model_volume_id) const override { return model_volume_id == volume_id; } + }; + + /// + /// Unproject on mesh by Mesh raycasters + /// Note: Function use current camera position from wxGetApp() + /// + /// Position of mouse on screen + /// Define which caster will be skipped, null mean no skip + /// Position on surface, normal direction and transformation key, which define hitted object instance + std::optional unproject(const Vec2d &mouse_pos, + const ISkip *skip = nullptr) const; + + /// + /// Getter on transformation + /// + /// Define transformation + /// Transformation for key + Transform3d get_transformation(const TrKey &tr_key) const; +}; + +} // namespace Slic3r::GUI + +#endif // slic3r_RaycastManager_hpp_