Gui: Fix Linux SpaceMouse performance: event coalescing, camera batching, per-axis deadzone (#28110)
* Fix Linux SpaceMouse jerkiness: coalesce spnav events and batch camera updates On Linux, pollSpacenav() posts every spnav motion event individually (125-250Hz from spacenavd), each triggering a full scene redraw. Combined with camera->orientation and camera->position each triggering separate Coin3D notifications, this results in up to 500 redraws/sec. Fix 1 - Event coalescing (GuiNativeEventLinux.cpp): The drain loop now keeps only the latest motion state and posts once after all pending events are consumed. SpaceMouse events are absolute deflection (current puck position), not deltas, so the latest event always contains the complete state. Fix 2 - Batched camera updates (NavigationStyle.cpp): Wrap camera property changes in enableNotify(false/true) + touch() so Coin3D fires a single redraw instead of two per event. Together this reduces redraws from ~500/sec to ~60/sec (display refresh), eliminating the jerky navigation that Linux users experience with complex models. Fixes #25926 * Add per-axis deadzone filtering for Linux SpaceMouse Each SpaceMouse axis has different sensitivity and noise characteristics. A single global deadzone is too coarse — rotation axes may need different thresholds than translation axes. Read per-axis deadzone values from user.cfg (BaseApp/Spaceball/Motion) using keys PanLRDeadzone, PanUDDeadzone, ZoomDeadzone, TiltDeadzone, RollDeadzone, SpinDeadzone. Values default to 0 (no filtering). Axis values below their individual threshold are zeroed before posting the motion event, preventing unintended drift from sensor noise while preserving intentional small movements. This is the Linux equivalent of deadzone handling that NavLib provides on Windows/macOS. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use std::array for axisDeadzoneKeys to avoid magic number Replace C-style array with std::array and use .size() instead of hardcoded loop bound, as suggested in code review. * Cache deadzone values with ParameterGrp Observer instead of polling Replace per-event GetParameterGroupByPath + GetInt calls with a static DeadzoneCache that loads values once and updates them via ParameterGrp::ObserverType when user.cfg changes. Also use static constexpr for the keys array and std::abs for readability. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Move DeadzoneCache from static local to member variable Replace the static local DeadzoneCache in pollSpacenav() with a std::unique_ptr<DeadzoneCache> member of GuiNativeEvent, initialized in initSpaceball() after the spnav connection is established. Forward-declare in the header to keep includes minimal. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Maik-0000FF <Maik-0000FF@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -21,17 +21,70 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <FCConfig.h>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <App/Application.h>
|
||||
|
||||
#include "GuiNativeEventLinux.h"
|
||||
|
||||
#include "GuiApplicationNativeEventAware.h"
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Parameter.h>
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include <spnav.h>
|
||||
|
||||
// Cached per-axis deadzone values, auto-updated via Observer when user.cfg changes.
|
||||
class Gui::DeadzoneCache: public ParameterGrp::ObserverType
|
||||
{
|
||||
public:
|
||||
static constexpr std::array<const char*, 6> keys = {
|
||||
"PanLRDeadzone",
|
||||
"PanUDDeadzone",
|
||||
"ZoomDeadzone",
|
||||
"TiltDeadzone",
|
||||
"RollDeadzone",
|
||||
"SpinDeadzone",
|
||||
};
|
||||
|
||||
std::array<int, 6> values {};
|
||||
|
||||
explicit DeadzoneCache(ParameterGrp::handle hGrp)
|
||||
: hGrp(std::move(hGrp))
|
||||
{
|
||||
loadAll();
|
||||
this->hGrp->Attach(this);
|
||||
}
|
||||
|
||||
~DeadzoneCache() override
|
||||
{
|
||||
hGrp->Detach(this);
|
||||
}
|
||||
|
||||
void OnChange(ParameterGrp::SubjectType& /*rCaller*/, ParameterGrp::MessageType reason) override
|
||||
{
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
if (std::strcmp(reason, keys[i]) == 0) {
|
||||
values[i] = static_cast<int>(hGrp->GetInt(keys[i], 0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void loadAll()
|
||||
{
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
values[i] = static_cast<int>(hGrp->GetInt(keys[i], 0));
|
||||
}
|
||||
}
|
||||
|
||||
ParameterGrp::handle hGrp;
|
||||
};
|
||||
|
||||
Gui::GuiNativeEvent::GuiNativeEvent(Gui::GUIApplicationNativeEventAware* app)
|
||||
: GuiAbstractNativeEvent(app)
|
||||
{}
|
||||
@@ -60,6 +113,9 @@ void Gui::GuiNativeEvent::initSpaceball(QMainWindow* window)
|
||||
QSocketNotifier* SpacenavNotifier
|
||||
= new QSocketNotifier(spnav_fd(), QSocketNotifier::Read, this);
|
||||
connect(SpacenavNotifier, SIGNAL(activated(int)), this, SLOT(pollSpacenav()));
|
||||
dzCache = std::make_unique<DeadzoneCache>(
|
||||
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Spaceball/Motion")
|
||||
);
|
||||
mainApp->setSpaceballPresent(true);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +123,7 @@ void Gui::GuiNativeEvent::initSpaceball(QMainWindow* window)
|
||||
void Gui::GuiNativeEvent::pollSpacenav()
|
||||
{
|
||||
spnav_event ev;
|
||||
bool hasMotion = false;
|
||||
while (spnav_poll_event(&ev)) {
|
||||
switch (ev.type) {
|
||||
case SPNAV_EVENT_MOTION: {
|
||||
@@ -76,7 +133,7 @@ void Gui::GuiNativeEvent::pollSpacenav()
|
||||
motionDataArray[3] = -ev.motion.rx;
|
||||
motionDataArray[4] = -ev.motion.rz;
|
||||
motionDataArray[5] = -ev.motion.ry;
|
||||
mainApp->postMotionEvent(motionDataArray);
|
||||
hasMotion = true;
|
||||
break;
|
||||
}
|
||||
case SPNAV_EVENT_BUTTON: {
|
||||
@@ -85,6 +142,19 @@ void Gui::GuiNativeEvent::pollSpacenav()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasMotion) {
|
||||
// Per-axis deadzone: zero out axes below their individual threshold.
|
||||
// Values cached and auto-updated via Observer when user.cfg changes.
|
||||
if (dzCache) {
|
||||
for (size_t i = 0; i < dzCache->values.size(); i++) {
|
||||
int dz = dzCache->values[i];
|
||||
if (dz > 0 && std::abs(motionDataArray[i]) < dz) {
|
||||
motionDataArray[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
mainApp->postMotionEvent(motionDataArray);
|
||||
}
|
||||
}
|
||||
|
||||
#include "3Dconnexion/moc_GuiNativeEventLinux.cpp"
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "GuiAbstractNativeEvent.h"
|
||||
#include <memory>
|
||||
|
||||
class QMainWindow;
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class GUIApplicationNativeEventAware;
|
||||
class DeadzoneCache;
|
||||
|
||||
class GuiNativeEvent: public GuiAbstractNativeEvent
|
||||
{
|
||||
@@ -42,6 +44,9 @@ private:
|
||||
GuiNativeEvent();
|
||||
GuiNativeEvent(const GuiNativeEvent&);
|
||||
GuiNativeEvent& operator=(const GuiNativeEvent&);
|
||||
|
||||
std::unique_ptr<DeadzoneCache> dzCache;
|
||||
|
||||
private Q_SLOTS:
|
||||
void pollSpacenav();
|
||||
};
|
||||
|
||||
@@ -1934,9 +1934,14 @@ SbBool NavigationStyle::processMotionEvent(const SoMotion3Event* const ev)
|
||||
newRotation.multVec(SbVec3f(0.0, 0.0, -1.0), newDirection);
|
||||
newPosition = center - (newDirection * camera->focalDistance.getValue());
|
||||
|
||||
newRotation.multVec(dir, dir);
|
||||
SbVec3f finalPosition = newPosition + (dir * translationFactor);
|
||||
|
||||
camera->enableNotify(false);
|
||||
camera->orientation.setValue(newRotation);
|
||||
camera->orientation.getValue().multVec(dir, dir);
|
||||
camera->position = newPosition + (dir * translationFactor);
|
||||
camera->position = finalPosition;
|
||||
camera->enableNotify(true);
|
||||
camera->touch();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user