PrusaSlicer-NonPlainar/src/slic3r/GUI/Mouse3DController.hpp
bubnikv 25d58faaad WIP: Windows specific 3Dconnexion using WM_INPUT.
This implementation works with the 3DConnexion driver (sic!)
if PrusaSlicer.xml is stored into c:\Program Files\3Dconnexion\3DxWare\3DxWinCore64\Cfg\

The implementation is inspired with Blender, see code inside WITH_INPUT_NDOF blocks.
2020-03-31 09:01:55 +02:00

237 lines
10 KiB
C++

#ifndef slic3r_Mouse3DController_hpp_
#define slic3r_Mouse3DController_hpp_
// Enabled debug output to console and extended imgui dialog
#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 0
#include "libslic3r/Point.hpp"
#include "hidapi.h"
#include <queue>
#include <thread>
#include <vector>
#include <chrono>
#include <condition_variable>
#include <tbb/mutex.h>
namespace Slic3r {
class AppConfig;
namespace GUI {
struct Camera;
class GLCanvas3D;
class Mouse3DController
{
// Parameters, which are configured by the ImGUI dialog when pressing Ctrl+M.
// The UI thread modifies a copy of the parameters and indicates to the background thread that there was a change
// to copy the parameters.
struct Params
{
static constexpr double DefaultTranslationScale = 2.5;
static constexpr double MaxTranslationDeadzone = 0.2;
static constexpr double DefaultTranslationDeadzone = 0.5 * MaxTranslationDeadzone;
static constexpr float DefaultRotationScale = 1.0f;
static constexpr float MaxRotationDeadzone = 0.2f;
static constexpr float DefaultRotationDeadzone = 0.5f * MaxRotationDeadzone;
static constexpr double DefaultZoomScale = 0.1;
template <typename Number>
struct CustomParameters
{
Number scale;
Number deadzone;
};
CustomParameters<double> translation { DefaultTranslationScale, DefaultTranslationDeadzone };
CustomParameters<float> rotation { DefaultRotationScale, DefaultRotationDeadzone };
CustomParameters<double> zoom { DefaultZoomScale, 0.0 };
// Do not process button presses from 3DConnexion device, let the user map the 3DConnexion keys in 3DConnexion driver.
bool buttons_enabled { false };
// The default value of 15 for max_size seems to work fine on all platforms
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
// and playing with the imgui dialog which shows by pressing CTRL+M
size_t input_queue_max_size { 15 };
// Whether to swap Y/Z axes or not.
bool swap_yz{ false };
};
// Queue of the 3DConnexion input events (translations, rotations, button presses).
class State
{
public:
struct QueueItem {
static QueueItem translation(const Vec3d &translation) { QueueItem out; out.vector = translation; out.type_or_buttons = TranslationType; return out; }
static QueueItem rotation(const Vec3d &rotation) { QueueItem out; out.vector = rotation; out.type_or_buttons = RotationType; return out; }
static QueueItem buttons(unsigned int buttons) { QueueItem out; out.type_or_buttons = buttons; return out; }
bool is_translation() const { return this->type_or_buttons == TranslationType; }
bool is_rotation() const { return this->type_or_buttons == RotationType; }
bool is_buttons() const { return ! this->is_translation() && ! this->is_rotation(); }
Vec3d vector;
unsigned int type_or_buttons;
static constexpr unsigned int TranslationType = std::numeric_limits<unsigned int>::max();
static constexpr unsigned int RotationType = TranslationType - 1;
};
private:
// m_input_queue is accessed by the background thread and by the UI thread. Access to m_input_queue
// is guarded with m_input_queue_mutex.
std::deque<QueueItem> m_input_queue;
mutable tbb::mutex m_input_queue_mutex;
#ifdef WIN32
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
// We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
// by triggering unwanted zoom in/out of the scene
// The following variable is used to count the potential mouse wheel events triggered and is updated by:
// Mouse3DController::collect_input() through the call to the append_rotation() method
// GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
// GLCanvas3D::on_idle() through the call to the apply() method
unsigned int m_mouse_wheel_counter { 0 };
#endif /* WIN32 */
public:
// Called by the background thread or by by Mouse3DHandlerMac.mm when a new event is received from 3DConnexion device.
void append_translation(const Vec3d& translation, size_t input_queue_max_size);
void append_rotation(const Vec3f& rotation, size_t input_queue_max_size);
void append_button(unsigned int id, size_t input_queue_max_size);
#ifdef WIN32
// Called by GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
// to filter out spurious mouse scroll events produced by the 3DConnexion driver on Windows.
bool process_mouse_wheel();
#endif /* WIN32 */
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
Vec3d get_first_vector_of_type(unsigned int type) const {
tbb::mutex::scoped_lock lock(m_input_queue_mutex);
auto it = std::find_if(m_input_queue.begin(), m_input_queue.end(), [type](const QueueItem& item) { return item.type_or_buttons == type; });
return (it == m_input_queue.end()) ? Vec3d::Zero() : it->vector;
}
size_t input_queue_size_current() const {
tbb::mutex::scoped_lock lock(m_input_queue_mutex);
return m_input_queue.size();
}
std::atomic<size_t> input_queue_max_size_achieved;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
// Apply the 3DConnexion events stored in the input queue, reset the input queue.
// Returns true if any change to the camera took place.
bool apply(const Params &params, Camera& camera);
};
// Background thread works with this copy.
Params m_params;
// UI thread will read / write this copy.
Params m_params_ui;
bool m_params_ui_changed { false };
mutable tbb::mutex m_params_ui_mutex;
// This is a database of parametes of all 3DConnexion devices ever connected.
// This database is loaded from AppConfig on application start and it is stored to AppConfig on application exit.
// We need to do that as the AppConfig is not thread safe and we need read the parameters on device connect / disconnect,
// which is now done by a background thread.
std::map<std::string, Params> m_params_by_device;
mutable State m_state;
std::atomic<bool> m_connected { false };
std::string m_device_str;
#if ! __APPLE__
// Worker thread for enumerating devices, connecting, reading data from the device and closing the device.
std::thread m_thread;
hid_device* m_device { nullptr };
// Using m_stop_condition_mutex to synchronize m_stop.
bool m_stop { false };
#ifdef _WIN32
std::atomic<bool> m_wakeup { false };
#endif /* _WIN32 */
// Mutex and condition variable for sleeping during the detection of 3DConnexion devices by polling while allowing
// cancellation before the end of the polling interval.
std::mutex m_stop_condition_mutex;
std::condition_variable m_stop_condition;
#endif
// Is the ImGUI dialog shown? Accessed from UI thread only.
mutable bool m_show_settings_dialog { false };
// Set to true when ther user closes the dialog by clicking on [X] or [Close] buttons. Accessed from UI thread only.
mutable bool m_settings_dialog_closed_by_user { false };
public:
// Load the device parameter database from appconfig. To be called on application startup.
void load_config(const AppConfig &appconfig);
// Store the device parameter database back to appconfig. To be called on application closeup.
void save_config(AppConfig &appconfig) const;
// Start the background thread to detect and connect to a HID device (Windows and Linux).
// Connect to a 3DConnextion driver (OSX).
// Call load_config() before init().
void init();
// Stop the background thread (Windows and Linux).
// Disconnect from a 3DConnextion driver (OSX).
// Call save_config() after shutdown().
void shutdown();
bool connected() const { return m_connected; }
#if __APPLE__
// Interfacing with the Objective C code (MouseHandlerMac.mm)
void connected(std::string device_name);
void disconnected();
typedef std::array<double, 6> DataPacketAxis;
// Unpack a 3DConnexion packet provided by the 3DConnexion driver into m_state. Called by Mouse3DHandlerMac.mm
bool handle_input(const DataPacketAxis& packet);
#endif // __APPLE__
#ifdef WIN32
bool handle_raw_input_win32(const unsigned char *data, const int packet_lenght);
// Called by Win32 HID enumeration callback.
void device_attached(const std::string &device);
// On Windows, the 3DConnexion driver sends out mouse wheel rotation events to an active application
// if the application does not register at the driver. This is a workaround to ignore these superfluous
// mouse wheel events.
bool process_mouse_wheel() { return m_state.process_mouse_wheel(); }
#endif // WIN32
// Apply the received 3DConnexion mouse events to the camera. Called from the UI rendering thread.
bool apply(Camera& camera);
bool is_settings_dialog_shown() const { return m_show_settings_dialog; }
void show_settings_dialog(bool show) { m_show_settings_dialog = show && this->connected(); }
void render_settings_dialog(GLCanvas3D& canvas) const;
#if ! __APPLE__
private:
bool connect_device();
void disconnect_device();
// secondary thread methods
void run();
void collect_input();
typedef std::array<unsigned char, 13> DataPacketRaw;
// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
static bool handle_input(const DataPacketRaw& packet, const int packet_length, const Params &params, State &state_in_out);
// The following is called by handle_input() from the worker thread.
static bool handle_packet(const DataPacketRaw& packet, const int packet_length, const Params &params, State &state_in_out);
static bool handle_packet_translation(const DataPacketRaw& packet, const Params &params, State &state_in_out);
static bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params &params, State &state_in_out);
static bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params &params, State &state_in_out);
#endif /* __APPLE__ */
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_Mouse3DController_hpp_