diff options
author | bubnikv <bubnikv@gmail.com> | 2020-03-04 13:36:36 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2020-03-04 13:36:36 +0300 |
commit | 0b96855c2ee2ed0aa2a57890be70679ee7882a00 (patch) | |
tree | 365dd694af8217e79c6a9ec451a88c2b0f3a9fb6 /src/slic3r/GUI/Mouse3DController.cpp | |
parent | a87ba5d6a6912a06999d0b1f4e19280969b30ddf (diff) |
Reworked the 3DConnexion interfacing code to run the device
enumeration / connect / disconnect and read out at the background
thread only.
Diffstat (limited to 'src/slic3r/GUI/Mouse3DController.cpp')
-rw-r--r-- | src/slic3r/GUI/Mouse3DController.cpp | 676 |
1 files changed, 369 insertions, 307 deletions
diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index c92557417..a7c66e591 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -53,205 +53,163 @@ static const std::vector<int> _3DCONNEXION_DEVICES = namespace Slic3r { namespace GUI { - -const double Mouse3DController::State::DefaultTranslationScale = 2.5; -const double Mouse3DController::State::MaxTranslationDeadzone = 0.2; -const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone; -const float Mouse3DController::State::DefaultRotationScale = 1.0f; -const float Mouse3DController::State::MaxRotationDeadzone = 0.2f; -const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone; -const double Mouse3DController::State::DefaultZoomScale = 0.1; - -Mouse3DController::State::State() - : m_buttons_enabled(false) - , m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone) - , m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone) - , m_zoom_params(DefaultZoomScale, 0.0) - , m_mouse_wheel_counter(0) + #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - , m_translation_queue_max_size(0) - , m_rotation_queue_max_size(0) - , m_buttons_queue_max_size(0) -#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT +template<typename T> +void update_maximum(std::atomic<T>& maximum_value, T const& value) noexcept { + T prev_value = maximum_value; + while (prev_value < value && ! maximum_value.compare_exchange_weak(prev_value, value)) ; } +#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT -void Mouse3DController::State::append_translation(const Vec3d& translation) +void Mouse3DController::State::append_translation(const Vec3d& translation, size_t input_queue_max_size) { - while (m_translation.queue.size() >= m_translation.max_size) - { - m_translation.queue.pop(); - } - m_translation.queue.push(translation); + tbb::mutex::scoped_lock lock(m_input_queue_mutex); + while (m_input_queue.size() >= input_queue_max_size) + m_input_queue.pop_front(); + m_input_queue.emplace_back(QueueItem::translation(translation)); #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size()); + update_maximum(input_queue_max_size_achieved, m_input_queue.size()); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT } -void Mouse3DController::State::append_rotation(const Vec3f& rotation) +void Mouse3DController::State::append_rotation(const Vec3f& rotation, size_t input_queue_max_size) { - while (m_rotation.queue.size() >= m_rotation.max_size) - { - m_rotation.queue.pop(); - } - m_rotation.queue.push(rotation); + tbb::mutex::scoped_lock lock(m_input_queue_mutex); + while (m_input_queue.size() >= input_queue_max_size) + m_input_queue.pop_front(); + m_input_queue.emplace_back(QueueItem::rotation(rotation.cast<double>())); +#ifdef WIN32 + if (rotation.x() != 0.0f) + ++ m_mouse_wheel_counter; +#endif // WIN32 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size()); + update_maximum(input_queue_max_size_achieved, m_input_queue.size()); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - if (rotation(0) != 0.0f) - ++m_mouse_wheel_counter; } -void Mouse3DController::State::append_button(unsigned int id) +void Mouse3DController::State::append_button(unsigned int id, size_t /* input_queue_max_size */) { - if (!m_buttons_enabled) - return; - - m_buttons.push(id); + tbb::mutex::scoped_lock lock(m_input_queue_mutex); + m_input_queue.emplace_back(QueueItem::buttons(id)); #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size()); + update_maximum(input_queue_max_size_achieved, m_input_queue.size()); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT } +#ifdef WIN32 +// Filter out mouse scroll events produced by the 3DConnexion driver. bool Mouse3DController::State::process_mouse_wheel() { - if (m_mouse_wheel_counter.load() == 0) + tbb::mutex::scoped_lock lock(m_input_queue_mutex); + if (m_mouse_wheel_counter == 0) + // No 3DConnexion rotation has been captured since the last mouse scroll event. return false; - else if (!m_rotation.queue.empty()) - { - --m_mouse_wheel_counter; + if (std::find_if(m_input_queue.begin(), m_input_queue.end(), [](const QueueItem &item){ return item.is_rotation(); }) != m_input_queue.end()) { + // There is a rotation stored in the queue. Suppress one mouse scroll event. + -- m_mouse_wheel_counter; return true; } - - m_mouse_wheel_counter.store(0); + m_mouse_wheel_counter = 0; return true; } +#endif // WIN32 -void Mouse3DController::State::set_queues_max_size(size_t size) +bool Mouse3DController::State::apply(const Mouse3DController::Params ¶ms, Camera& camera) { - if (size > 0) - { - m_translation.max_size = size; - m_rotation.max_size = size; - -#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_translation_queue_max_size = 0; - m_rotation_queue_max_size = 0; - m_buttons_queue_max_size = 0; -#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - } -} - -bool Mouse3DController::State::apply(Camera& camera) -{ - if (!wxGetApp().IsActive()) + if (! wxGetApp().IsActive()) return false; - bool ret = false; - - if (has_translation()) - { - const Vec3d& translation = m_translation.queue.front(); - double zoom_factor = camera.min_zoom() / camera.get_zoom(); - camera.set_target(camera.get_target() + zoom_factor * m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up())); - if (translation(1) != 0.0) - camera.update_zoom(m_zoom_params.scale * translation(1) / std::abs(translation(1))); - m_translation.queue.pop(); - ret = true; - } - - if (has_rotation()) + std::deque<QueueItem> input_queue; { - Vec3d rot = (m_rotation_params.scale * m_rotation.queue.front()).cast<double>() * (PI / 180.); - camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); - m_rotation.queue.pop(); - ret = true; + // Atomically move m_input_queue to input_queue. + tbb::mutex::scoped_lock lock(m_input_queue_mutex); + input_queue = std::move(m_input_queue); + m_input_queue.clear(); } - if (m_buttons_enabled && has_button()) - { - unsigned int button = m_buttons.front(); - switch (button) - { - case 0: { camera.update_zoom(1.0); break; } - case 1: { camera.update_zoom(-1.0); break; } - default: { break; } - } - m_buttons.pop(); - ret = true; + for (const QueueItem &input_queue_item : input_queue) { + if (input_queue_item.is_translation()) { + const Vec3d& translation = input_queue_item.vector; + double zoom_factor = camera.min_zoom() / camera.get_zoom(); + camera.set_target(camera.get_target() + zoom_factor * params.translation.scale * (translation.x() * camera.get_dir_right() + translation.z() * camera.get_dir_up())); + if (translation.y() != 0.0) + camera.update_zoom(params.zoom.scale * translation.y() / std::abs(translation.y())); + } else if (input_queue_item.is_rotation()) { + Vec3d rot = params.rotation.scale * input_queue_item.vector * (PI / 180.); + camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); + break; + } else { + assert(input_queue_item.is_buttons()); + switch (input_queue_item.type_or_buttons) { + case 0: camera.update_zoom(1.0); break; + case 1: camera.update_zoom(-1.0); break; + default: break; + } + } } - return ret; -} - -Mouse3DController::Mouse3DController() - : m_initialized(false) - , m_device(nullptr) - , m_device_str("") - , m_running(false) - , m_show_settings_dialog(false) - , m_mac_mouse_connected(false) - , m_settings_dialog_closed_by_user(false) -#if __APPLE__ - ,m_handler_mac(new Mouse3DHandlerMac(this)) -#endif //__APPLE__ -{ - m_last_time = std::chrono::high_resolution_clock::now(); + return ! input_queue.empty(); } -void Mouse3DController::init() +// Load the device parameter database from appconfig. To be called on application startup. +void Mouse3DController::load_config(const AppConfig &appconfig) { - if (m_initialized) - return; - - // Initialize the hidapi library - int res = hid_init(); - if (res != 0) - { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library"; - return; - } - - m_initialized = true; + // We do not synchronize m_params_by_device with the background thread explicitely + // as there should be a full memory barrier executed once the background thread is started. + m_params_by_device.clear(); + + for (const std::string &device_name : appconfig.get_mouse_device_names()) { + double translation_speed = 4.0; + float rotation_speed = 4.0; + double translation_deadzone = Params::DefaultTranslationDeadzone; + float rotation_deadzone = Params::DefaultRotationDeadzone; + double zoom_speed = 2.0; + appconfig.get_mouse_device_translation_speed(device_name, translation_speed); + appconfig.get_mouse_device_translation_deadzone(device_name, translation_deadzone); + appconfig.get_mouse_device_rotation_speed(device_name, rotation_speed); + appconfig.get_mouse_device_rotation_deadzone(device_name, rotation_deadzone); + appconfig.get_mouse_device_zoom_speed(device_name, zoom_speed); + // clamp to valid values + Params params; + params.translation.scale = Params::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0); + params.translation.deadzone = std::clamp(translation_deadzone, 0.0, Params::MaxTranslationDeadzone); + params.rotation.scale = Params::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f); + params.rotation.deadzone = std::clamp(rotation_deadzone, 0.0f, Params::MaxRotationDeadzone); + params.zoom.scale = Params::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0); + m_params_by_device[device_name] = std::move(params); + } } -void Mouse3DController::shutdown() +// Store the device parameter database back to appconfig. To be called on application closeup. +void Mouse3DController::save_config(AppConfig &appconfig) const { - if (!m_initialized) - return; - - stop(); - disconnect_device(); - - // Finalize the hidapi library - hid_exit(); - m_initialized = false; + // We do not synchronize m_params_by_device with the background thread explicitely + // as there should be a full memory barrier executed once the background thread is stopped. + for (const std::pair<std::string, Params> &key_value_pair : m_params_by_device) { + const std::string &device_name = key_value_pair.first; + const Params ¶ms = key_value_pair.second; + // Store current device parameters into the config + appconfig.set_mouse_device(device_name, params.translation.scale / Params::DefaultTranslationScale, params.translation.deadzone, + params.rotation.scale / Params::DefaultRotationScale, params.rotation.deadzone, params.zoom.scale / Params::DefaultZoomScale); + } } bool Mouse3DController::apply(Camera& camera) { - if (!m_initialized) - return false; - // check if the user unplugged the device - if (!is_running() && is_device_connected()) - { - disconnect_device(); + if (! m_connected) { // hides the settings dialog if the user un-plug the device m_show_settings_dialog = false; m_settings_dialog_closed_by_user = false; } - - // check if the user plugged the device - if (connect_device()) - start(); - - return is_device_connected() ? m_state.apply(camera) : false; + return m_state.apply(m_params, camera); } void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const { - if (!is_running() || !m_show_settings_dialog) + if (! m_show_settings_dialog || ! m_connected) return; // when the user clicks on [X] or [Close] button we need to trigger @@ -264,6 +222,13 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const return; } + Params params_copy; + bool params_changed = false; + { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + params_copy = m_params_ui; + } + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -296,30 +261,40 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text(_(L("Speed:"))); ImGui::PopStyleColor(); - float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; - if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); + float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale; + if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) { + params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale; + params_changed = true; + } - float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; - if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); + float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale; + if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) { + params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale; + params_changed = true; + } - float zoom_scale = m_state.get_zoom_scale() / State::DefaultZoomScale; - if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_zoom_scale(State::DefaultZoomScale * zoom_scale); + float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale; + if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) { + params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale; + params_changed = true; + } ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, color); imgui.text(_(L("Deadzone:"))); ImGui::PopStyleColor(); - float translation_deadzone = (float)m_state.get_translation_deadzone(); - if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f")) - m_state.set_translation_deadzone((double)translation_deadzone); + float translation_deadzone = (float)params_copy.translation.deadzone; + if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { + params_copy.translation.deadzone = (double)translation_deadzone; + params_changed = true; + } - float rotation_deadzone = m_state.get_rotation_deadzone(); - if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f")) - m_state.set_rotation_deadzone(rotation_deadzone); + float rotation_deadzone = params_copy.rotation.deadzone; + if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) { + params_copy.rotation.deadzone = rotation_deadzone; + params_changed = true; + } #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); @@ -328,8 +303,8 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text("DEBUG:"); imgui.text("Vectors:"); ImGui::PopStyleColor(); - Vec3f translation = m_state.get_translation().cast<float>(); - Vec3f rotation = m_state.get_rotation(); + Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>(); + Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>(); ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); @@ -337,19 +312,16 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text("Queue size:"); ImGui::PopStyleColor(); - int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() }; - int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() }; - int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() }; - - ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly); - ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly); - ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly); + int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) }; + ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly); - int queue_size = (int)m_state.get_queues_max_size(); - if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly)) + int input_queue_size_param = int(params_copy.input_queue_max_size); + if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) { - if (queue_size > 0) - m_state.set_queues_max_size(queue_size); + if (input_queue_size_param > 0) { + params_copy.input_queue_max_size = input_queue_size_param; + params_changed = true; + } } ImGui::Separator(); @@ -377,23 +349,169 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } imgui.end(); + + if (params_changed) { + // Synchronize front end parameters to back end. + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + auto pthis = const_cast<Mouse3DController*>(this); +#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT + if (params_copy.input_queue_max_size != params_copy.input_queue_max_size) + // Reset the statistics counter. + m_state.input_queue_max_size_achieved = 0; +#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT + pthis->m_params_ui = params_copy; + pthis->m_params_ui_changed = true; + } } -bool Mouse3DController::connect_device() +#if __APPLE__ + +void Mouse3DController::connected(std::string device_name) { -#ifdef __APPLE__ - return false; -#endif//__APPLE__ - static const long long DETECTION_TIME_MS = 2000; // two seconds + m_device_str = device_name; + // Copy the parameters for m_device_str into the current parameters. + if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + m_params = m_params_ui = it_params->second; + } + m_connected = true; +} - if (is_device_connected()) - return false; +void Mouse3DController::disconnected() +{ + // Copy the current parameters for m_device_str into the parameter database. + assert(! m_device_str.empty()); + if (! m_device_str.empty()) { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + m_params_by_device[m_device_str] = m_params_ui; + } + m_device_str.clear(); + m_connected = false; +} - // check time since last detection took place - if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS) - return false; +bool Mouse3DController::handle_input(const DataPacketAxis& packet, const Params ¶ms, State &state_in_out) +{ + if (! wxGetApp().IsActive()) + return; + + { + // Synchronize parameters between the UI thread and the background thread. + //FIXME is this necessary on OSX? Are these notifications triggered from the main thread or from a worker thread? + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + if (m_params_ui_changed) { + m_params = m_params_ui; + m_params_ui_changed = false; + } + } - m_last_time = std::chrono::high_resolution_clock::now(); + bool updated = false; + //translation + double deadzone = params.translation.deadzone; + Vec3d translation(std::abs(packet[0]) > deadzone ? -packet[0] : 0.0, + std::abs(packet[1]) > deadzone ? packet[1] : 0.0, + std::abs(packet[2]) > deadzone ? packet[2] : 0.0); + if (!translation.isApprox(Vec3d::Zero())) + { + state_in_out.append_translation(translation, params.input_queue_max_size); + updated = true; + } + //rotation + deadzone = params.rotation.deadzone; + Vec3f rotation(std::abs(packet[3]) > deadzone ? (float)packet[3] : 0.0, + std::abs(packet[4]) > deadzone ? (float)packet[4] : 0.0, + std::abs(packet[5]) > deadzone ? (float)packet[5] : 0.0); + if (!rotation.isApprox(Vec3f::Zero())) + { + state_in_out.append_rotation(rotation, params.input_queue_max_size); + updated = true; + } + +#if 1 + if (updated) { + wxGetApp().plater()->set_current_canvas_as_dirty(); + // ask for an idle event to update 3D scene + wxWakeUpIdle(); + } +#endif + return updated; +} + +#else //__APPLE__ + +// Initialize the application. +void Mouse3DController::init() +{ + assert(! m_thread.joinable()); + if (! m_thread.joinable()) { + m_stop = false; + m_thread = std::thread(&Mouse3DController::run, this); + } +} + +// Closing the application. +void Mouse3DController::shutdown() +{ + if (m_thread.joinable()) { + // Stop the worker thread, if running. + { + // Notify the worker thread to cancel wait on detection polling. + std::unique_lock<std::mutex> lock(m_stop_condition_mutex); + m_stop = true; + m_stop_condition.notify_all(); + } + // Wait for the worker thread to stop. + m_thread.join(); + } +} + +// Main routine of the worker thread. +void Mouse3DController::run() +{ + // Initialize the hidapi library + int res = hid_init(); + if (res != 0) { + // Give up. + BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library"; + return; + } + + for (;;) { + { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + if (m_stop) + break; + if (m_params_ui_changed) { + m_params = m_params_ui; + m_params_ui_changed = false; + } + } + if (m_device == nullptr) + // Polls the HID devices, blocks for maximum 2 seconds. + m_connected = this->connect_device(); + else + // Waits for 3DConnexion mouse input for maximum 100ms, then repeats. + this->collect_input(); + } + + this->disconnect_device(); + + // Finalize the hidapi library + hid_exit(); +} + +bool Mouse3DController::connect_device() +{ + if (m_stop) + return false; + + { + // Wait for 2 seconds, but cancellable by m_stop. + std::unique_lock<std::mutex> lock(m_stop_condition_mutex); + m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return this->m_stop; }); + } + + if (m_stop) + return false; // Enumerates devices hid_device_info* devices = hid_enumerate(0, 0); @@ -623,23 +741,11 @@ bool Mouse3DController::connect_device() #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT std::cout << "Opened device." << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - // get device parameters from the config, if present - double translation_speed = 4.0; - float rotation_speed = 4.0; - double translation_deadzone = State::DefaultTranslationDeadzone; - float rotation_deadzone = State::DefaultRotationDeadzone; - double zoom_speed = 2.0; - wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed); - wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone); - wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed); - wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone); - wxGetApp().app_config->get_mouse_device_zoom_speed(m_device_str, zoom_speed); - // clamp to valid values - m_state.set_translation_scale(State::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0)); - m_state.set_translation_deadzone(std::clamp(translation_deadzone, 0.0, State::MaxTranslationDeadzone)); - m_state.set_rotation_scale(State::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f)); - m_state.set_rotation_deadzone(std::clamp(rotation_deadzone, 0.0f, State::MaxRotationDeadzone)); - m_state.set_zoom_scale(State::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0)); + // Copy the parameters for m_device_str into the current parameters. + if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + m_params = m_params_ui = it_params->second; + } } #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT else @@ -657,138 +763,85 @@ bool Mouse3DController::connect_device() void Mouse3DController::disconnect_device() { - if (!is_device_connected()) - return; - - // Stop the secondary thread, if running - if (m_thread.joinable()) - m_thread.join(); - - // Store current device parameters into the config - wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(), - m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone(), m_state.get_zoom_scale() / State::DefaultZoomScale); - - wxGetApp().app_config->save(); - - // Close the 3Dconnexion device - hid_close(m_device); - m_device = nullptr; - - BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str; - - m_device_str = ""; -} - -void Mouse3DController::start() -{ - if (!is_device_connected() || m_running) - return; - - m_thread = std::thread(&Mouse3DController::run, this); + if (m_device) { + hid_close(m_device); + m_device = nullptr; + BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str; + // Copy the current parameters for m_device_str into the parameter database. + { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + m_params_by_device[m_device_str] = m_params_ui; + } + m_device_str.clear(); + m_connected = false; + } } -void Mouse3DController::run() -{ - m_running = true; - while (m_running) - { - collect_input(); - } -} void Mouse3DController::collect_input() { DataPacketRaw packet = { 0 }; + // Read packet, block maximum 100 ms. That means when closing the application, closing the application will be delayed by 100 ms. int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100); - if (res < 0) - { - // An error occourred (device detached from pc ?) - stop(); - return; - } - handle_input(packet, res); + if (res < 0) { + // An error occourred (device detached from pc ?). Close the 3Dconnexion device. + this->disconnect_device(); + } else + this->handle_input(packet, res, m_params, m_state); } - -void Mouse3DController::handle_input_axis(const DataPacketAxis& packet) -{ - if (!wxGetApp().IsActive()) - return; - bool appended = false; - //translation - double deadzone = m_state.get_translation_deadzone(); - Vec3d translation(std::abs(packet[0]) > deadzone ? -packet[0] : 0.0, - std::abs(packet[1]) > deadzone ? packet[1] : 0.0, - std::abs(packet[2]) > deadzone ? packet[2] : 0.0); - if (!translation.isApprox(Vec3d::Zero())) - { - m_state.append_translation(translation); - appended = true; - } - //rotation - deadzone = m_state.get_rotation_deadzone(); - Vec3f rotation(std::abs(packet[3]) > deadzone ? (float)packet[3] : 0.0, - std::abs(packet[4]) > deadzone ? (float)packet[4] : 0.0, - std::abs(packet[5]) > deadzone ? (float)packet[5] : 0.0); - if (!rotation.isApprox(Vec3f::Zero())) - { - m_state.append_rotation(rotation); - appended = true; - } - if (appended) - { - wxGetApp().plater()->set_current_canvas_as_dirty(); - // ask for an idle event to update 3D scene - wxWakeUpIdle(); - } -} -void Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_lenght) + +// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread. +bool Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_lenght, const Params ¶ms, State &state_in_out) { - if (!wxGetApp().IsActive()) - return; + if (! wxGetApp().IsActive()) + return false; int res = packet_lenght; bool updated = false; if (res == 7) - updated = handle_packet(packet); + updated = handle_packet(packet, params, state_in_out); else if (res == 13) - updated = handle_wireless_packet(packet); + updated = handle_wireless_packet(packet, params, state_in_out); else if ((res == 3) && (packet[0] == 3)) // On Mac button packets can be 3 bytes long - updated = handle_packet(packet); + updated = handle_packet(packet, params, state_in_out); #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT else if (res > 0) std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - if (updated) - { +#if 1 + if (updated) { wxGetApp().plater()->set_current_canvas_as_dirty(); // ask for an idle event to update 3D scene wxWakeUpIdle(); } +#endif + return updated; } -bool Mouse3DController::handle_packet(const DataPacketRaw& packet) +// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by handle_input() from the worker thread. +bool Mouse3DController::handle_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out) { switch (packet[0]) { case 1: // Translation { - if (handle_packet_translation(packet)) + if (handle_packet_translation(packet, params, state_in_out)) return true; break; } case 2: // Rotation { - if (handle_packet_rotation(packet, 1)) + if (handle_packet_rotation(packet, 1, params, state_in_out)) return true; break; } case 3: // Button { - if (handle_packet_button(packet, packet.size() - 1)) + if (params.buttons_enabled && handle_packet_button(packet, packet.size() - 1, params, state_in_out)) return true; break; @@ -796,14 +849,14 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet) case 23: // Battery charge { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl; + std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } default: { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl; + std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } @@ -812,14 +865,15 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet) return false; } -bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) +// Unpack raw 3DConnexion HID packet of a wireless 3D mouse into m_state. Called by handle_input() from the worker thread. +bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out) { switch (packet[0]) { case 1: // Translation + Rotation { - bool updated = handle_packet_translation(packet); - updated |= handle_packet_rotation(packet, 7); + bool updated = handle_packet_translation(packet, params, state_in_out); + updated |= handle_packet_rotation(packet, 7, params, state_in_out); if (updated) return true; @@ -828,7 +882,7 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) } case 3: // Button { - if (handle_packet_button(packet, 12)) + if (params.buttons_enabled && handle_packet_button(packet, 12, params, state_in_out)) return true; break; @@ -836,14 +890,14 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) case 23: // Battery charge { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl; + std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } default: { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl; + std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } @@ -852,46 +906,52 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) return false; } -double convert_input(unsigned char first, unsigned char second, double deadzone) +// Convert a signed 16bit word from a 3DConnexion mouse HID packet into a double coordinate, apply a dead zone. +static double convert_input(int coord_byte_low, int coord_byte_high, double deadzone) { - short value = first | second << 8; + int value = coord_byte_low | (coord_byte_high << 8); + if (value >= 32768) + value = value - 65536; double ret = (double)value / 350.0; return (std::abs(ret) > deadzone) ? ret : 0.0; } -bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet) +// Unpack raw 3DConnexion HID packet, decode state of translation axes into state_in_out. Called by handle_input() from the worker thread. +bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out) { - double deadzone = m_state.get_translation_deadzone(); + double deadzone = params.translation.deadzone; Vec3d translation(-convert_input(packet[1], packet[2], deadzone), convert_input(packet[3], packet[4], deadzone), convert_input(packet[5], packet[6], deadzone)); if (!translation.isApprox(Vec3d::Zero())) { - m_state.append_translation(translation); + state_in_out.append_translation(translation, params.input_queue_max_size); return true; } return false; } -bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte) +// Unpack raw 3DConnexion HID packet, decode state of rotation axes into state_in_out. Called by the handle_input() from worker thread. +bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params ¶ms, State &state_in_out) { - double deadzone = (double)m_state.get_rotation_deadzone(); + double deadzone = (double)params.rotation.deadzone; Vec3f rotation((float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone), (float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone), (float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone)); if (!rotation.isApprox(Vec3f::Zero())) { - m_state.append_rotation(rotation); + state_in_out.append_rotation(rotation, params.input_queue_max_size); return true; } return false; } -bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size) +// Unpack raw 3DConnexion HID packet, decode button state into state_in_out. Called by handle_input() from the worker thread. +bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params ¶ms, State &state_in_out) { unsigned int data = 0; for (unsigned int i = 1; i < packet_size; ++i) @@ -904,7 +964,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign { if (data_bits.test(i)) { - m_state.append_button((unsigned int)i); + state_in_out.append_button((unsigned int)i, params.input_queue_max_size); return true; } } @@ -912,5 +972,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign return false; } +#endif //__APPLE__ + } // namespace GUI } // namespace Slic3r |