consistent Starting/InProgress/Finishing ndof events with dead-zone filtering
authorMike Erwin <significant.bit@gmail.com>
Mon, 18 Jul 2011 22:42:09 +0000 (22:42 +0000)
committerMike Erwin <significant.bit@gmail.com>
Mon, 18 Jul 2011 22:42:09 +0000 (22:42 +0000)
intern/ghost/intern/GHOST_NDOFManager.cpp
intern/ghost/intern/GHOST_NDOFManager.h
intern/ghost/intern/GHOST_NDOFManagerX11.cpp
intern/ghost/intern/GHOST_SystemX11.cpp

index e001540aa9596b9d848a5421568bcefbfd830698..06c792128cd8accf4f22b4eecc06d433b73e7c75 100644 (file)
@@ -149,6 +149,7 @@ GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System& sys)
        , m_prevMotionTime(0)
        , m_motionState(GHOST_kNotStarted)
        , m_motionEventPending(false)
+       , m_deadZone(0.f)
        {
        // to avoid the rare situation where one triple is updated and
        // the other is not, initialize them both here:
@@ -201,7 +202,7 @@ void GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short produ
                                }
                        break;
                default:
-                       printf("ndof: unknown vendor %04hx\n", vendor_id);
+                       printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
                }
 
        m_buttonMask = ~(-1 << m_buttonCount);
@@ -211,41 +212,18 @@ void GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short produ
        #endif
        }
 
-void GHOST_NDOFManager::updateMotionState()
-       {
-       if (m_motionEventPending)
-               return;
-
-       switch (m_motionState)
-               {
-               case GHOST_kFinished:
-               case GHOST_kNotStarted:
-                       m_motionState = GHOST_kStarting;
-                       break;
-               case GHOST_kStarting:
-                       m_motionState = GHOST_kInProgress;
-                       break;          
-               default:
-                       // InProgress remains InProgress
-                       // should never be Finishing
-                       break;
-               }
-
-       m_motionEventPending = true;
-       }
-
 void GHOST_NDOFManager::updateTranslation(short t[3], GHOST_TUns64 time)
        {
        memcpy(m_translation, t, sizeof(m_translation));
        m_motionTime = time;
-       updateMotionState();
+       m_motionEventPending = true;
        }
 
 void GHOST_NDOFManager::updateRotation(short r[3], GHOST_TUns64 time)
        {
        memcpy(m_rotation, r, sizeof(m_rotation));
        m_motionTime = time;
-       updateMotionState();
+       m_motionEventPending = true;
        }
 
 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
@@ -338,6 +316,20 @@ void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
                }
        }
 
+void GHOST_NDOFManager::setDeadZone(float dz)
+       {
+       if (dz < 0.f)
+               // negative values don't make sense, so clamp at zero
+               dz = 0.f;
+       else if (dz > 0.5f)
+               // warn the rogue user/programmer, but allow it
+               printf("ndof: dead zone of %.2f is rather high...\n", dz);
+
+       m_deadZone = dz;
+
+       printf("ndof: dead zone set to %.2f\n", dz);
+       }
+
 static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
        {
        #define HOME(foo) (ndof->foo == 0)
@@ -346,16 +338,25 @@ static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
 
 static bool nearHomePosition(GHOST_TEventNDOFMotionData* ndof, float threshold)
        {
-       #define HOME1(foo) (fabsf(ndof->foo) < threshold)
-       return HOME1(tx) && HOME1(ty) && HOME1(tz) && HOME1(rx) && HOME1(ry) && HOME1(rz);
+       if (threshold == 0.f)
+               return atHomePosition(ndof);
+       else
+               {
+               #define HOME1(foo) (fabsf(ndof->foo) < threshold)
+               return HOME1(tx) && HOME1(ty) && HOME1(tz) && HOME1(rx) && HOME1(ry) && HOME1(rz);
+               }
        }
 
 bool GHOST_NDOFManager::sendMotionEvent()
        {
-       if (m_motionState == GHOST_kFinished || m_motionState == GHOST_kNotStarted)
+       if (!m_motionEventPending)
                return false;
 
+       m_motionEventPending = false; // any pending motion is handled right now
+
        GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
+       if (window == NULL)
+               return false; // delivery will fail, so don't bother sending
 
        GHOST_EventNDOFMotion* event = new GHOST_EventNDOFMotion(m_motionTime, window);
        GHOST_TEventNDOFMotionData* data = (GHOST_TEventNDOFMotionData*) event->getData();
@@ -363,7 +364,7 @@ bool GHOST_NDOFManager::sendMotionEvent()
        const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually
 
        // probable future enhancement
-       // scale *= m_sensitivity;
+       // scale *= U.ndof_sensitivity;
 
        data->tx = scale * m_translation[0];
        data->ty = scale * m_translation[1];
@@ -373,35 +374,57 @@ bool GHOST_NDOFManager::sendMotionEvent()
        data->ry = scale * m_rotation[1];
        data->rz = scale * m_rotation[2];
 
-       if (m_motionState == GHOST_kStarting)
-               // prev motion time will be ancient, so just make up something reasonable
-               data->dt = 0.0125f;
-       else
-               data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
+       data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
 
-       m_prevMotionTime = m_motionTime;
+       bool handMotion = !nearHomePosition(data, m_deadZone);
 
-       // 'at rest' test goes at the end so that the first 'rest' event gets sent
-       if (atHomePosition(data))
-//     if (nearHomePosition(data, 0.05f)) // Linux & Windows have trouble w/ calibration
+       // determine what kind of motion event to send (Starting, InProgress, Finishing)
+       // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
+       switch (m_motionState)
                {
-               data->progress = GHOST_kFinishing;
-               // for internal state, skip Finishing & jump to Finished
-               m_motionState = GHOST_kFinished;
+               case GHOST_kNotStarted:
+               case GHOST_kFinished:
+                       if (handMotion)
+                               {
+                               data->progress = GHOST_kStarting;
+                               m_motionState = GHOST_kInProgress;
+                               // prev motion time will be ancient, so just make up something reasonable
+                               data->dt = 0.0125f;
+                               }
+                       else
+                               {
+                               // send no event and keep current state
+                               delete event;
+                               return false;
+                               }
+                       break;
+               case GHOST_kInProgress:
+                       if (handMotion)
+                               {
+                               data->progress = GHOST_kInProgress;
+                               // keep InProgress state
+                               }
+                       else
+                               {
+                               data->progress = GHOST_kFinishing;
+                               m_motionState = GHOST_kFinished;
+                               }
+                       break;
                }
-       else
-               data->progress = m_motionState; // Starting or InProgress
 
        #ifdef DEBUG_NDOF_MOTION
-       printf("ndof %s: T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
-               progress_string[data->progress],
+       printf("ndof motion sent -- %s\n", progress_string[data->progress]);
+
+       // show details about this motion event
+       printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
                data->tx, data->ty, data->tz,
                data->rx, data->ry, data->rz,
                data->dt);
        #endif
 
        m_system.pushEvent(event);
-       m_motionEventPending = false;
+
+       m_prevMotionTime = m_motionTime;
 
        return true;
        }
index 79e3a4eb4c81007ee114a49f43c54ea10058dd10..9c67daa44125d116938f17b10235a1cb9daa8fa7 100644 (file)
 // #define DEBUG_NDOF_MOTION
 #define DEBUG_NDOF_BUTTONS
 
-typedef enum { NDOF_UnknownDevice, NDOF_SpaceNavigator, NDOF_SpaceExplorer, NDOF_SpacePilotPro } NDOF_DeviceT;
+typedef enum {
+       NDOF_UnknownDevice, // <-- motion will work fine, buttons are ignored
+       NDOF_SpaceNavigator,
+       NDOF_SpaceExplorer,
+       NDOF_SpacePilotPro
+       } NDOF_DeviceT;
 
 // NDOF device button event types
 typedef enum {
@@ -50,6 +55,7 @@ typedef enum {
        NDOF_BUTTON_ISO1,
        NDOF_BUTTON_ISO2,
        // 90 degree rotations
+       // these don't all correspond to physical buttons
        NDOF_BUTTON_ROLL_CW,
        NDOF_BUTTON_ROLL_CCW,
        NDOF_BUTTON_SPIN_CW,
@@ -63,6 +69,7 @@ typedef enum {
        NDOF_BUTTON_PLUS,
        NDOF_BUTTON_MINUS,
        // general-purpose buttons
+       // TODO: expose these to keymap editor so users can assign functions
        NDOF_BUTTON_1,
        NDOF_BUTTON_2,
        NDOF_BUTTON_3,
@@ -90,6 +97,12 @@ public:
        // use standard USB/HID identifiers
        void setDevice(unsigned short vendor_id, unsigned short product_id);
 
+       // filter out small/accidental/uncalibrated motions by
+       // setting up a "dead zone" around home position
+       // set to 0 to disable
+       // 0.1 is a safe and reasonable value
+       void setDeadZone(float);
+
        // the latest raw axis data from the device
        // NOTE: axis data should be in blender view coordinates
        //       +X is to the right
@@ -118,7 +131,6 @@ protected:
 private:
        void sendButtonEvent(NDOF_ButtonT, bool press, GHOST_TUns64 time, GHOST_IWindow*);
        void sendKeyEvent(GHOST_TKey, bool press, GHOST_TUns64 time, GHOST_IWindow*);
-       void updateMotionState();
 
        NDOF_DeviceT m_deviceType;
        int m_buttonCount;
@@ -130,8 +142,10 @@ private:
 
        GHOST_TUns64 m_motionTime; // in milliseconds
        GHOST_TUns64 m_prevMotionTime; // time of most recent Motion event sent
+
        GHOST_TProgress m_motionState;
        bool m_motionEventPending;
+       float m_deadZone; // discard motion with each component < this
 };
 
 #endif
index cc2b2f785ea2b56cb515840172a7bc7ea4d678c8..a823c3c8a81e943d27e515d2e6fe96ff2141596f 100644 (file)
@@ -34,6 +34,8 @@ GHOST_NDOFManagerX11::GHOST_NDOFManagerX11(GHOST_System& sys)
                {
                m_available = true;
 
+               setDeadZone(0.1f); // how to calibrate on Linux? throw away slight motion!
+
                // determine exactly which device is plugged in
 
                #define MAX_LINE_LENGTH 100
index 4c62e5e62b4cd0dd997c18eb5a610365833bae6a..f9b365fd62e24e2ab3438c81f159d17aa7a51cf9 100644 (file)
@@ -597,7 +597,8 @@ GHOST_SystemX11::processEvent(XEvent *xe)
                {
                        XFocusChangeEvent &xfe = xe->xfocus;
 
-                       printf("X: focus %s for window %d\n", xfe.type == FocusIn ? "in" : "out", (int) xfe.window);
+                       // TODO: make sure this is the correct place for activate/deactivate
+                       // printf("X: focus %s for window %d\n", xfe.type == FocusIn ? "in" : "out", (int) xfe.window);
                
                        // May have to look at the type of event and filter some
                        // out.
@@ -687,7 +688,7 @@ GHOST_SystemX11::processEvent(XEvent *xe)
                                );
                        }
 
-                       printf("X: %s window %d\n", xce.type == EnterNotify ? "entering" : "leaving", (int) xce.window);
+                       // printf("X: %s window %d\n", xce.type == EnterNotify ? "entering" : "leaving", (int) xce.window);
 
                        if (xce.type == EnterNotify)
                                m_windowManager->setActiveWindow(window);