Blender  V2.59
GHOST_NDOFManager.cpp
Go to the documentation of this file.
00001 /*
00002  * $Id: GHOST_NDOFManager.cpp 39153 2011-08-07 16:44:10Z merwin $
00003  *
00004  * ***** BEGIN GPL LICENSE BLOCK *****
00005  *
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version. 
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software Foundation,
00018  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  *
00020  * Contributor(s):
00021  *   Mike Erwin
00022  *
00023  * ***** END GPL LICENSE BLOCK *****
00024  */
00025 
00026 #include "GHOST_Debug.h"
00027 #include "GHOST_NDOFManager.h"
00028 #include "GHOST_EventNDOF.h"
00029 #include "GHOST_EventKey.h"
00030 #include "GHOST_WindowManager.h"
00031 #include <string.h> // for memory functions
00032 #include <stdio.h> // for error/info reporting
00033 #include <math.h>
00034 
00035 #ifdef DEBUG_NDOF_MOTION
00036 // printable version of each GHOST_TProgress value
00037 static const char* progress_string[] =
00038         {"not started","starting","in progress","finishing","finished"};
00039 #endif
00040 
00041 #ifdef DEBUG_NDOF_BUTTONS
00042 static const char* ndof_button_names[] = {
00043         // used internally, never sent
00044         "NDOF_BUTTON_NONE",
00045         // these two are available from any 3Dconnexion device
00046         "NDOF_BUTTON_MENU",
00047         "NDOF_BUTTON_FIT",
00048         // standard views
00049         "NDOF_BUTTON_TOP",
00050         "NDOF_BUTTON_BOTTOM",
00051         "NDOF_BUTTON_LEFT",
00052         "NDOF_BUTTON_RIGHT",
00053         "NDOF_BUTTON_FRONT",
00054         "NDOF_BUTTON_BACK",
00055         // more views
00056         "NDOF_BUTTON_ISO1",
00057         "NDOF_BUTTON_ISO2",
00058         // 90 degree rotations
00059         "NDOF_BUTTON_ROLL_CW",
00060         "NDOF_BUTTON_ROLL_CCW",
00061         "NDOF_BUTTON_SPIN_CW",
00062         "NDOF_BUTTON_SPIN_CCW",
00063         "NDOF_BUTTON_TILT_CW",
00064         "NDOF_BUTTON_TILT_CCW",
00065         // device control
00066         "NDOF_BUTTON_ROTATE",
00067         "NDOF_BUTTON_PANZOOM",
00068         "NDOF_BUTTON_DOMINANT",
00069         "NDOF_BUTTON_PLUS",
00070         "NDOF_BUTTON_MINUS",
00071         // general-purpose buttons
00072         "NDOF_BUTTON_1",
00073         "NDOF_BUTTON_2",
00074         "NDOF_BUTTON_3",
00075         "NDOF_BUTTON_4",
00076         "NDOF_BUTTON_5",
00077         "NDOF_BUTTON_6",
00078         "NDOF_BUTTON_7",
00079         "NDOF_BUTTON_8",
00080         "NDOF_BUTTON_9",
00081         "NDOF_BUTTON_10",
00082 };
00083 #endif
00084 
00085 static const NDOF_ButtonT SpaceNavigator_HID_map[] = {
00086         NDOF_BUTTON_MENU,
00087         NDOF_BUTTON_FIT
00088 };
00089 
00090 static const NDOF_ButtonT SpaceExplorer_HID_map[] = {
00091         NDOF_BUTTON_1,
00092         NDOF_BUTTON_2,
00093         NDOF_BUTTON_TOP,
00094         NDOF_BUTTON_LEFT,
00095         NDOF_BUTTON_RIGHT,
00096         NDOF_BUTTON_FRONT,
00097         NDOF_BUTTON_NONE, // esc key
00098         NDOF_BUTTON_NONE, // alt key
00099         NDOF_BUTTON_NONE, // shift key
00100         NDOF_BUTTON_NONE, // ctrl key
00101         NDOF_BUTTON_FIT,
00102         NDOF_BUTTON_MENU,
00103         NDOF_BUTTON_PLUS,
00104         NDOF_BUTTON_MINUS,
00105         NDOF_BUTTON_ROTATE
00106 };
00107 
00108 static const NDOF_ButtonT SpacePilotPro_HID_map[] = {
00109         NDOF_BUTTON_MENU,
00110         NDOF_BUTTON_FIT,
00111         NDOF_BUTTON_TOP,
00112         NDOF_BUTTON_LEFT,
00113         NDOF_BUTTON_RIGHT,
00114         NDOF_BUTTON_FRONT,
00115         NDOF_BUTTON_BOTTOM,
00116         NDOF_BUTTON_BACK,
00117         NDOF_BUTTON_ROLL_CW,
00118         NDOF_BUTTON_ROLL_CCW,
00119         NDOF_BUTTON_ISO1,
00120         NDOF_BUTTON_ISO2,
00121         NDOF_BUTTON_1,
00122         NDOF_BUTTON_2,
00123         NDOF_BUTTON_3,
00124         NDOF_BUTTON_4,
00125         NDOF_BUTTON_5,
00126         NDOF_BUTTON_6,
00127         NDOF_BUTTON_7,
00128         NDOF_BUTTON_8,
00129         NDOF_BUTTON_9,
00130         NDOF_BUTTON_10,
00131         NDOF_BUTTON_NONE, // esc key
00132         NDOF_BUTTON_NONE, // alt key
00133         NDOF_BUTTON_NONE, // shift key
00134         NDOF_BUTTON_NONE, // ctrl key
00135         NDOF_BUTTON_ROTATE,
00136         NDOF_BUTTON_PANZOOM,
00137         NDOF_BUTTON_DOMINANT,
00138         NDOF_BUTTON_PLUS,
00139         NDOF_BUTTON_MINUS
00140 };
00141 
00142 /* this is the older SpacePilot (sans Pro)
00143  * thanks to polosson for the info in this table */
00144 static const NDOF_ButtonT SpacePilot_HID_map[] = {
00145         NDOF_BUTTON_1,
00146         NDOF_BUTTON_2,
00147         NDOF_BUTTON_3,
00148         NDOF_BUTTON_4,
00149         NDOF_BUTTON_5,
00150         NDOF_BUTTON_6,
00151         NDOF_BUTTON_TOP,
00152         NDOF_BUTTON_LEFT,
00153         NDOF_BUTTON_RIGHT,
00154         NDOF_BUTTON_FRONT,
00155         NDOF_BUTTON_NONE, // esc key
00156         NDOF_BUTTON_NONE, // alt key
00157         NDOF_BUTTON_NONE, // shift key
00158         NDOF_BUTTON_NONE, // ctrl key
00159         NDOF_BUTTON_FIT,
00160         NDOF_BUTTON_MENU,
00161         NDOF_BUTTON_PLUS,
00162         NDOF_BUTTON_MINUS,
00163         NDOF_BUTTON_DOMINANT,
00164         NDOF_BUTTON_ROTATE,
00165         NDOF_BUTTON_NONE // the CONFIG button -- what does it do?
00166 };
00167 
00168 GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System& sys)
00169         : m_system(sys)
00170         , m_deviceType(NDOF_UnknownDevice) // each platform has its own device detection code
00171         , m_buttonCount(0)
00172         , m_buttonMask(0)
00173         , m_buttons(0)
00174         , m_motionTime(0)
00175         , m_prevMotionTime(0)
00176         , m_motionState(GHOST_kNotStarted)
00177         , m_motionEventPending(false)
00178         , m_deadZone(0.f)
00179 {
00180         // to avoid the rare situation where one triple is updated and
00181         // the other is not, initialize them both here:
00182         memset(m_translation, 0, sizeof(m_translation));
00183         memset(m_rotation, 0, sizeof(m_rotation));
00184 }
00185 
00186 bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short product_id)
00187 {
00188         // default to NDOF_UnknownDevice so rogue button events will get discarded
00189         // "mystery device" owners can help build a HID_map for their hardware
00190 
00191         switch (vendor_id) {
00192                 case 0x046D: // Logitech (3Dconnexion)
00193                         switch (product_id) {
00194                                 // -- current devices --
00195                                 case 0xC626:
00196                                         puts("ndof: using SpaceNavigator");
00197                                         m_deviceType = NDOF_SpaceNavigator;
00198                                         m_buttonCount = 2;
00199                                         break;
00200                                 case 0xC628:
00201                                         puts("ndof: using SpaceNavigator for Notebooks");
00202                                         m_deviceType = NDOF_SpaceNavigator; // for Notebooks
00203                                         m_buttonCount = 2;
00204                                         break;
00205                                 case 0xC627:
00206                                         puts("ndof: using SpaceExplorer");
00207                                         m_deviceType = NDOF_SpaceExplorer;
00208                                         m_buttonCount = 15;
00209                                         break;
00210                                 case 0xC629:
00211                                         puts("ndof: using SpacePilotPro");
00212                                         m_deviceType = NDOF_SpacePilotPro;
00213                                         m_buttonCount = 31;
00214                                         break;
00215 
00216                                 // -- older devices --
00217                                 case 0xC625:
00218                                         puts("ndof: using SpacePilot");
00219                                         m_deviceType = NDOF_SpacePilot;
00220                                         m_buttonCount = 21;
00221                                         break;
00222 
00223                                 case 0xC623:
00224                                         puts("ndof: SpaceTraveler not supported, please file a bug report");
00225                                         m_buttonCount = 8;
00226                                         break;
00227 
00228                                 default:
00229                                         printf("ndof: unknown Logitech product %04hx\n", product_id);
00230                         }
00231                         break;
00232                 default:
00233                         printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
00234         }
00235 
00236         if (m_deviceType == NDOF_UnknownDevice) {
00237                 return false;
00238         }
00239         else {
00240                 m_buttonMask = ~(-1 << m_buttonCount);
00241 
00242 #ifdef DEBUG_NDOF_BUTTONS
00243                 printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask);
00244 #endif
00245 
00246                 return true;
00247         }
00248 }
00249 
00250 void GHOST_NDOFManager::updateTranslation(short t[3], GHOST_TUns64 time)
00251 {
00252         memcpy(m_translation, t, sizeof(m_translation));
00253         m_motionTime = time;
00254         m_motionEventPending = true;
00255 }
00256 
00257 void GHOST_NDOFManager::updateRotation(short r[3], GHOST_TUns64 time)
00258 {
00259         memcpy(m_rotation, r, sizeof(m_rotation));
00260         m_motionTime = time;
00261         m_motionEventPending = true;
00262 }
00263 
00264 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
00265 {
00266         GHOST_EventNDOFButton* event = new GHOST_EventNDOFButton(time, window);
00267         GHOST_TEventNDOFButtonData* data = (GHOST_TEventNDOFButtonData*) event->getData();
00268 
00269         data->action = press ? GHOST_kPress : GHOST_kRelease;
00270         data->button = button;
00271 
00272 #ifdef DEBUG_NDOF_BUTTONS
00273         printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released");
00274 #endif
00275 
00276         m_system.pushEvent(event);
00277 }
00278 
00279 void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
00280 {
00281         GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
00282         GHOST_EventKey* event = new GHOST_EventKey(time, type, window, key);
00283 
00284 #ifdef DEBUG_NDOF_BUTTONS
00285         printf("keyboard %s\n", press ? "down" : "up");
00286 #endif
00287 
00288         m_system.pushEvent(event);
00289 }
00290 
00291 void GHOST_NDOFManager::updateButton(int button_number, bool press, GHOST_TUns64 time)
00292 {
00293         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
00294 
00295 #ifdef DEBUG_NDOF_BUTTONS
00296         if (m_deviceType != NDOF_UnknownDevice)
00297                 printf("ndof: button %d -> ", button_number);
00298 #endif
00299 
00300         switch (m_deviceType) {
00301                 case NDOF_SpaceNavigator:
00302                         sendButtonEvent(SpaceNavigator_HID_map[button_number], press, time, window);
00303                         break;
00304                 case NDOF_SpaceExplorer:
00305                         switch (button_number) {
00306                                 case 6: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
00307                                 case 7: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
00308                                 case 8: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
00309                                 case 9: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
00310                                 default: sendButtonEvent(SpaceExplorer_HID_map[button_number], press, time, window);
00311                         }
00312                         break;
00313                 case NDOF_SpacePilotPro:
00314                         switch (button_number) {
00315                                 case 22: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
00316                                 case 23: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
00317                                 case 24: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
00318                                 case 25: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
00319                                 default: sendButtonEvent(SpacePilotPro_HID_map[button_number], press, time, window);
00320                         }
00321                         break;
00322                 case NDOF_SpacePilot:
00323                         switch (button_number) {
00324                                 case 10: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
00325                                 case 11: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
00326                                 case 12: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
00327                                 case 13: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
00328                                 case 20: puts("ndof: ignoring CONFIG button"); break;
00329                                 default: sendButtonEvent(SpacePilot_HID_map[button_number], press, time, window);
00330                         }
00331                         break;
00332                 case NDOF_UnknownDevice:
00333                         printf("ndof: button %d on unknown device (ignoring)\n", button_number);
00334         }
00335 
00336         int mask = 1 << button_number;
00337         if (press) {
00338                 m_buttons |= mask; // set this button's bit
00339         }
00340         else {
00341                 m_buttons &= ~mask; // clear this button's bit
00342         }
00343 }
00344 
00345 void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
00346 {
00347         button_bits &= m_buttonMask; // discard any "garbage" bits
00348 
00349         int diff = m_buttons ^ button_bits;
00350 
00351         for (int button_number = 0; button_number < m_buttonCount; ++button_number) {
00352                 int mask = 1 << button_number;
00353 
00354                 if (diff & mask) {
00355                         bool press = button_bits & mask;
00356                         updateButton(button_number, press, time);
00357                 }
00358         }
00359 }
00360 
00361 void GHOST_NDOFManager::setDeadZone(float dz)
00362 {
00363         if (dz < 0.f) {
00364                 // negative values don't make sense, so clamp at zero
00365                 dz = 0.f;
00366         }
00367         else if (dz > 0.5f) {
00368                 // warn the rogue user/programmer, but allow it
00369                 printf("ndof: dead zone of %.2f is rather high...\n", dz);
00370         }
00371         m_deadZone = dz;
00372 
00373         printf("ndof: dead zone set to %.2f\n", dz);
00374 }
00375 
00376 static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
00377 {
00378 #define HOME(foo) (ndof->foo == 0.f)
00379         return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
00380 #undef HOME
00381 }
00382 
00383 static bool nearHomePosition(GHOST_TEventNDOFMotionData* ndof, float threshold)
00384 {
00385         if (threshold == 0.f) {
00386                 return atHomePosition(ndof);
00387         }
00388         else {
00389 #define HOME(foo) (fabsf(ndof->foo) < threshold)
00390                 return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
00391 #undef HOME
00392         }
00393 }
00394 
00395 bool GHOST_NDOFManager::sendMotionEvent()
00396 {
00397         if (!m_motionEventPending)
00398                 return false;
00399 
00400         m_motionEventPending = false; // any pending motion is handled right now
00401 
00402         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
00403 
00404         if (window == NULL) {
00405                 return false; // delivery will fail, so don't bother sending
00406         }
00407 
00408         GHOST_EventNDOFMotion* event = new GHOST_EventNDOFMotion(m_motionTime, window);
00409         GHOST_TEventNDOFMotionData* data = (GHOST_TEventNDOFMotionData*) event->getData();
00410 
00411         // scale axis values here to normalize them to around +/- 1
00412         // they are scaled again for overall sensitivity in the WM based on user prefs
00413 
00414         const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually
00415 
00416         data->tx = scale * m_translation[0];
00417         data->ty = scale * m_translation[1];
00418         data->tz = scale * m_translation[2];
00419 
00420         data->rx = scale * m_rotation[0];
00421         data->ry = scale * m_rotation[1];
00422         data->rz = scale * m_rotation[2];
00423 
00424         data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
00425 
00426         bool weHaveMotion = !nearHomePosition(data, m_deadZone);
00427 
00428         // determine what kind of motion event to send (Starting, InProgress, Finishing)
00429         // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
00430         switch (m_motionState) {
00431                 case GHOST_kNotStarted:
00432                 case GHOST_kFinished:
00433                         if (weHaveMotion) {
00434                                 data->progress = GHOST_kStarting;
00435                                 m_motionState = GHOST_kInProgress;
00436                                 // prev motion time will be ancient, so just make up a reasonable time delta
00437                                 data->dt = 0.0125f;
00438                         }
00439                         else {
00440                                 // send no event and keep current state
00441                                 delete event;
00442                                 return false;
00443                         }
00444                         break;
00445                 case GHOST_kInProgress:
00446                         if (weHaveMotion) {
00447                                 data->progress = GHOST_kInProgress;
00448                                 // remain 'InProgress'
00449                         }
00450                         else {
00451                                 data->progress = GHOST_kFinishing;
00452                                 m_motionState = GHOST_kFinished;
00453                         }
00454                         break;
00455                 default:
00456                         ; // will always be one of the above
00457         }
00458 
00459 #ifdef DEBUG_NDOF_MOTION
00460         printf("ndof motion sent -- %s\n", progress_string[data->progress]);
00461 
00462         // show details about this motion event
00463         printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
00464                data->tx, data->ty, data->tz,
00465                data->rx, data->ry, data->rz,
00466                data->dt);
00467 #endif
00468 
00469         m_system.pushEvent(event);
00470 
00471         m_prevMotionTime = m_motionTime;
00472 
00473         return true;
00474 }