added support for the 3Dconnexion SpaceMouse Pro -- tested on Linux w/ spacenavd...
[blender.git] / intern / ghost / intern / GHOST_NDOFManager.cpp
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s):
19  *   Mike Erwin
20  *
21  * ***** END GPL LICENSE BLOCK *****
22  */
23
24 #include "GHOST_Debug.h"
25 #include "GHOST_NDOFManager.h"
26 #include "GHOST_EventNDOF.h"
27 #include "GHOST_EventKey.h"
28 #include "GHOST_WindowManager.h"
29 #include <string.h> // for memory functions
30 #include <stdio.h> // for error/info reporting
31 #include <math.h>
32
33 #ifdef DEBUG_NDOF_MOTION
34 // printable version of each GHOST_TProgress value
35 static const char* progress_string[] =
36         {"not started","starting","in progress","finishing","finished"};
37 #endif
38
39 #ifdef DEBUG_NDOF_BUTTONS
40 static const char* ndof_button_names[] = {
41         // used internally, never sent
42         "NDOF_BUTTON_NONE",
43         // these two are available from any 3Dconnexion device
44         "NDOF_BUTTON_MENU",
45         "NDOF_BUTTON_FIT",
46         // standard views
47         "NDOF_BUTTON_TOP",
48         "NDOF_BUTTON_BOTTOM",
49         "NDOF_BUTTON_LEFT",
50         "NDOF_BUTTON_RIGHT",
51         "NDOF_BUTTON_FRONT",
52         "NDOF_BUTTON_BACK",
53         // more views
54         "NDOF_BUTTON_ISO1",
55         "NDOF_BUTTON_ISO2",
56         // 90 degree rotations
57         "NDOF_BUTTON_ROLL_CW",
58         "NDOF_BUTTON_ROLL_CCW",
59         "NDOF_BUTTON_SPIN_CW",
60         "NDOF_BUTTON_SPIN_CCW",
61         "NDOF_BUTTON_TILT_CW",
62         "NDOF_BUTTON_TILT_CCW",
63         // device control
64         "NDOF_BUTTON_ROTATE",
65         "NDOF_BUTTON_PANZOOM",
66         "NDOF_BUTTON_DOMINANT",
67         "NDOF_BUTTON_PLUS",
68         "NDOF_BUTTON_MINUS",
69         // general-purpose buttons
70         "NDOF_BUTTON_1",
71         "NDOF_BUTTON_2",
72         "NDOF_BUTTON_3",
73         "NDOF_BUTTON_4",
74         "NDOF_BUTTON_5",
75         "NDOF_BUTTON_6",
76         "NDOF_BUTTON_7",
77         "NDOF_BUTTON_8",
78         "NDOF_BUTTON_9",
79         "NDOF_BUTTON_10",
80 };
81 #endif
82
83 static const NDOF_ButtonT SpaceNavigator_HID_map[] = {
84         NDOF_BUTTON_MENU,
85         NDOF_BUTTON_FIT
86 };
87
88 static const NDOF_ButtonT SpaceExplorer_HID_map[] = {
89         NDOF_BUTTON_1,
90         NDOF_BUTTON_2,
91         NDOF_BUTTON_TOP,
92         NDOF_BUTTON_LEFT,
93         NDOF_BUTTON_RIGHT,
94         NDOF_BUTTON_FRONT,
95         NDOF_BUTTON_NONE, // esc key
96         NDOF_BUTTON_NONE, // alt key
97         NDOF_BUTTON_NONE, // shift key
98         NDOF_BUTTON_NONE, // ctrl key
99         NDOF_BUTTON_FIT,
100         NDOF_BUTTON_MENU,
101         NDOF_BUTTON_PLUS,
102         NDOF_BUTTON_MINUS,
103         NDOF_BUTTON_ROTATE
104 };
105
106 static const NDOF_ButtonT SpacePilotPro_HID_map[] = {
107         NDOF_BUTTON_MENU,
108         NDOF_BUTTON_FIT,
109         NDOF_BUTTON_TOP,
110         NDOF_BUTTON_LEFT,
111         NDOF_BUTTON_RIGHT,
112         NDOF_BUTTON_FRONT,
113         NDOF_BUTTON_BOTTOM,
114         NDOF_BUTTON_BACK,
115         NDOF_BUTTON_ROLL_CW,
116         NDOF_BUTTON_ROLL_CCW,
117         NDOF_BUTTON_ISO1,
118         NDOF_BUTTON_ISO2,
119         NDOF_BUTTON_1,
120         NDOF_BUTTON_2,
121         NDOF_BUTTON_3,
122         NDOF_BUTTON_4,
123         NDOF_BUTTON_5,
124         NDOF_BUTTON_6,
125         NDOF_BUTTON_7,
126         NDOF_BUTTON_8,
127         NDOF_BUTTON_9,
128         NDOF_BUTTON_10,
129         NDOF_BUTTON_NONE, // esc key
130         NDOF_BUTTON_NONE, // alt key
131         NDOF_BUTTON_NONE, // shift key
132         NDOF_BUTTON_NONE, // ctrl key
133         NDOF_BUTTON_ROTATE,
134         NDOF_BUTTON_PANZOOM,
135         NDOF_BUTTON_DOMINANT,
136         NDOF_BUTTON_PLUS,
137         NDOF_BUTTON_MINUS
138 };
139
140 // latest HW: button-compatible with SpacePilotPro, just fewer of them
141 static const NDOF_ButtonT SpaceMousePro_HID_map[] = {
142         NDOF_BUTTON_MENU,
143         NDOF_BUTTON_FIT,
144         NDOF_BUTTON_TOP,
145         NDOF_BUTTON_NONE, // left
146         NDOF_BUTTON_RIGHT,
147         NDOF_BUTTON_FRONT,
148         NDOF_BUTTON_NONE, // bottom
149         NDOF_BUTTON_NONE, // back
150         NDOF_BUTTON_ROLL_CW,
151         NDOF_BUTTON_NONE, // roll ccw
152         NDOF_BUTTON_NONE, // iso 1
153         NDOF_BUTTON_NONE, // iso 2
154         NDOF_BUTTON_1,
155         NDOF_BUTTON_2,
156         NDOF_BUTTON_3,
157         NDOF_BUTTON_4,
158         NDOF_BUTTON_NONE, // 5
159         NDOF_BUTTON_NONE, // 6
160         NDOF_BUTTON_NONE, // 7
161         NDOF_BUTTON_NONE, // 8
162         NDOF_BUTTON_NONE, // 9
163         NDOF_BUTTON_NONE, // 10
164         NDOF_BUTTON_NONE, // esc key
165         NDOF_BUTTON_NONE, // alt key
166         NDOF_BUTTON_NONE, // shift key
167         NDOF_BUTTON_NONE, // ctrl key
168         NDOF_BUTTON_ROTATE,
169 };
170
171 /* this is the older SpacePilot (sans Pro)
172  * thanks to polosson for the info in this table */
173 static const NDOF_ButtonT SpacePilot_HID_map[] = {
174         NDOF_BUTTON_1,
175         NDOF_BUTTON_2,
176         NDOF_BUTTON_3,
177         NDOF_BUTTON_4,
178         NDOF_BUTTON_5,
179         NDOF_BUTTON_6,
180         NDOF_BUTTON_TOP,
181         NDOF_BUTTON_LEFT,
182         NDOF_BUTTON_RIGHT,
183         NDOF_BUTTON_FRONT,
184         NDOF_BUTTON_NONE, // esc key
185         NDOF_BUTTON_NONE, // alt key
186         NDOF_BUTTON_NONE, // shift key
187         NDOF_BUTTON_NONE, // ctrl key
188         NDOF_BUTTON_FIT,
189         NDOF_BUTTON_MENU,
190         NDOF_BUTTON_PLUS,
191         NDOF_BUTTON_MINUS,
192         NDOF_BUTTON_DOMINANT,
193         NDOF_BUTTON_ROTATE,
194         NDOF_BUTTON_NONE // the CONFIG button -- what does it do?
195 };
196
197 GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System& sys)
198         : m_system(sys)
199         , m_deviceType(NDOF_UnknownDevice) // each platform has its own device detection code
200         , m_buttonCount(0)
201         , m_buttonMask(0)
202         , m_buttons(0)
203         , m_motionTime(0)
204         , m_prevMotionTime(0)
205         , m_motionState(GHOST_kNotStarted)
206         , m_motionEventPending(false)
207         , m_deadZone(0.f)
208 {
209         // to avoid the rare situation where one triple is updated and
210         // the other is not, initialize them both here:
211         memset(m_translation, 0, sizeof(m_translation));
212         memset(m_rotation, 0, sizeof(m_rotation));
213 }
214
215 bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short product_id)
216 {
217         // default to NDOF_UnknownDevice so rogue button events will get discarded
218         // "mystery device" owners can help build a HID_map for their hardware
219
220         switch (vendor_id) {
221                 case 0x046D: // Logitech (3Dconnexion)
222                         switch (product_id) {
223                                 // -- current devices --
224                                 case 0xC626:
225                                         puts("ndof: using SpaceNavigator");
226                                         m_deviceType = NDOF_SpaceNavigator;
227                                         m_buttonCount = 2;
228                                         break;
229                                 case 0xC628:
230                                         puts("ndof: using SpaceNavigator for Notebooks");
231                                         m_deviceType = NDOF_SpaceNavigator; // for Notebooks
232                                         m_buttonCount = 2;
233                                         break;
234                                 case 0xC627:
235                                         puts("ndof: using SpaceExplorer");
236                                         m_deviceType = NDOF_SpaceExplorer;
237                                         m_buttonCount = 15;
238                                         break;
239                                 case 0xC629:
240                                         puts("ndof: using SpacePilotPro");
241                                         m_deviceType = NDOF_SpacePilotPro;
242                                         m_buttonCount = 31;
243                                         break;
244                                 case 0xC62B:
245                                         puts("ndof: using SpaceMousePro");
246                                         m_deviceType = NDOF_SpaceMousePro;
247                                         m_buttonCount = 27;
248                                         // ^^ actually has 15 buttons, but their HID codes range from 0 to 26
249                                         break;
250
251                                 // -- older devices --
252                                 case 0xC625:
253                                         puts("ndof: using SpacePilot");
254                                         m_deviceType = NDOF_SpacePilot;
255                                         m_buttonCount = 21;
256                                         break;
257
258                                 case 0xC623:
259                                         puts("ndof: SpaceTraveler not supported, please file a bug report");
260                                         m_buttonCount = 8;
261                                         break;
262
263                                 default:
264                                         printf("ndof: unknown Logitech product %04hx\n", product_id);
265                         }
266                         break;
267                 default:
268                         printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
269         }
270
271         if (m_deviceType == NDOF_UnknownDevice) {
272                 return false;
273         }
274         else {
275                 m_buttonMask = ~(-1 << m_buttonCount);
276
277                 // special case for SpaceMousePro? maybe...
278
279 #ifdef DEBUG_NDOF_BUTTONS
280                 printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask);
281 #endif
282
283                 return true;
284         }
285 }
286
287 void GHOST_NDOFManager::updateTranslation(short t[3], GHOST_TUns64 time)
288 {
289         memcpy(m_translation, t, sizeof(m_translation));
290         m_motionTime = time;
291         m_motionEventPending = true;
292 }
293
294 void GHOST_NDOFManager::updateRotation(short r[3], GHOST_TUns64 time)
295 {
296         memcpy(m_rotation, r, sizeof(m_rotation));
297         m_motionTime = time;
298         m_motionEventPending = true;
299 }
300
301 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
302 {
303         if (button == NDOF_BUTTON_NONE) {
304                 // just being exceptionally cautious...
305                 // air-tight button masking and proper function key emulation
306                 // should guarantee we never get to this point
307 #ifdef DEBUG_NDOF_BUTTONS
308                 printf("discarding NDOF_BUTTON_NONE (should not escape the NDOF manager)\n");
309 #endif
310                 return;
311         }
312
313         GHOST_EventNDOFButton* event = new GHOST_EventNDOFButton(time, window);
314         GHOST_TEventNDOFButtonData* data = (GHOST_TEventNDOFButtonData*) event->getData();
315
316         data->action = press ? GHOST_kPress : GHOST_kRelease;
317         data->button = button;
318
319 #ifdef DEBUG_NDOF_BUTTONS
320         printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released");
321 #endif
322
323         m_system.pushEvent(event);
324 }
325
326 void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
327 {
328         GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
329         GHOST_EventKey* event = new GHOST_EventKey(time, type, window, key);
330
331 #ifdef DEBUG_NDOF_BUTTONS
332         printf("keyboard %s\n", press ? "down" : "up");
333 #endif
334
335         m_system.pushEvent(event);
336 }
337
338 void GHOST_NDOFManager::updateButton(int button_number, bool press, GHOST_TUns64 time)
339 {
340         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
341
342 #ifdef DEBUG_NDOF_BUTTONS
343         if (m_deviceType != NDOF_UnknownDevice)
344                 printf("ndof: button %d -> ", button_number);
345 #endif
346
347         switch (m_deviceType) {
348                 case NDOF_SpaceNavigator:
349                         sendButtonEvent(SpaceNavigator_HID_map[button_number], press, time, window);
350                         break;
351                 case NDOF_SpaceExplorer:
352                         switch (button_number) {
353                                 case 6: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
354                                 case 7: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
355                                 case 8: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
356                                 case 9: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
357                                 default: sendButtonEvent(SpaceExplorer_HID_map[button_number], press, time, window);
358                         }
359                         break;
360                 case NDOF_SpacePilotPro:
361                         switch (button_number) {
362                                 case 22: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
363                                 case 23: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
364                                 case 24: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
365                                 case 25: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
366                                 default: sendButtonEvent(SpacePilotPro_HID_map[button_number], press, time, window);
367                         }
368                         break;
369                 case NDOF_SpaceMousePro:
370                         switch (button_number) {
371                                 case 22: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
372                                 case 23: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
373                                 case 24: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
374                                 case 25: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
375                                 default: sendButtonEvent(SpaceMousePro_HID_map[button_number], press, time, window);
376                         }
377                         break;
378                 case NDOF_SpacePilot:
379                         switch (button_number) {
380                                 case 10: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
381                                 case 11: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
382                                 case 12: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
383                                 case 13: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
384                                 case 20: puts("ndof: ignoring CONFIG button"); break;
385                                 default: sendButtonEvent(SpacePilot_HID_map[button_number], press, time, window);
386                         }
387                         break;
388                 case NDOF_UnknownDevice:
389                         printf("ndof: button %d on unknown device (ignoring)\n", button_number);
390         }
391
392         int mask = 1 << button_number;
393         if (press) {
394                 m_buttons |= mask; // set this button's bit
395         }
396         else {
397                 m_buttons &= ~mask; // clear this button's bit
398         }
399 }
400
401 void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
402 {
403         button_bits &= m_buttonMask; // discard any "garbage" bits
404
405         int diff = m_buttons ^ button_bits;
406
407         for (int button_number = 0; button_number < m_buttonCount; ++button_number) {
408                 int mask = 1 << button_number;
409
410                 if (diff & mask) {
411                         bool press = button_bits & mask;
412                         updateButton(button_number, press, time);
413                 }
414         }
415 }
416
417 void GHOST_NDOFManager::setDeadZone(float dz)
418 {
419         if (dz < 0.f) {
420                 // negative values don't make sense, so clamp at zero
421                 dz = 0.f;
422         }
423         else if (dz > 0.5f) {
424                 // warn the rogue user/programmer, but allow it
425                 GHOST_PRINTF("ndof: dead zone of %.2f is rather high...\n", dz);
426         }
427         m_deadZone = dz;
428
429         GHOST_PRINTF("ndof: dead zone set to %.2f\n", dz);
430 }
431
432 static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
433 {
434 #define HOME(foo) (ndof->foo == 0.f)
435         return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
436 #undef HOME
437 }
438
439 static bool nearHomePosition(GHOST_TEventNDOFMotionData* ndof, float threshold)
440 {
441         if (threshold == 0.f) {
442                 return atHomePosition(ndof);
443         }
444         else {
445 #define HOME(foo) (fabsf(ndof->foo) < threshold)
446                 return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
447 #undef HOME
448         }
449 }
450
451 bool GHOST_NDOFManager::sendMotionEvent()
452 {
453         if (!m_motionEventPending)
454                 return false;
455
456         m_motionEventPending = false; // any pending motion is handled right now
457
458         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
459
460         if (window == NULL) {
461                 return false; // delivery will fail, so don't bother sending
462         }
463
464         GHOST_EventNDOFMotion* event = new GHOST_EventNDOFMotion(m_motionTime, window);
465         GHOST_TEventNDOFMotionData* data = (GHOST_TEventNDOFMotionData*) event->getData();
466
467         // scale axis values here to normalize them to around +/- 1
468         // they are scaled again for overall sensitivity in the WM based on user prefs
469
470         const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually
471
472         data->tx = scale * m_translation[0];
473         data->ty = scale * m_translation[1];
474         data->tz = scale * m_translation[2];
475
476         data->rx = scale * m_rotation[0];
477         data->ry = scale * m_rotation[1];
478         data->rz = scale * m_rotation[2];
479
480         data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
481
482         bool weHaveMotion = !nearHomePosition(data, m_deadZone);
483
484         // determine what kind of motion event to send (Starting, InProgress, Finishing)
485         // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
486         switch (m_motionState) {
487                 case GHOST_kNotStarted:
488                 case GHOST_kFinished:
489                         if (weHaveMotion) {
490                                 data->progress = GHOST_kStarting;
491                                 m_motionState = GHOST_kInProgress;
492                                 // prev motion time will be ancient, so just make up a reasonable time delta
493                                 data->dt = 0.0125f;
494                         }
495                         else {
496                                 // send no event and keep current state
497                                 delete event;
498                                 return false;
499                         }
500                         break;
501                 case GHOST_kInProgress:
502                         if (weHaveMotion) {
503                                 data->progress = GHOST_kInProgress;
504                                 // remain 'InProgress'
505                         }
506                         else {
507                                 data->progress = GHOST_kFinishing;
508                                 m_motionState = GHOST_kFinished;
509                         }
510                         break;
511                 default:
512                         ; // will always be one of the above
513         }
514
515 #ifdef DEBUG_NDOF_MOTION
516         printf("ndof motion sent -- %s\n", progress_string[data->progress]);
517
518         // show details about this motion event
519         printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
520                data->tx, data->ty, data->tz,
521                data->rx, data->ry, data->rz,
522                data->dt);
523 #endif
524
525         m_system.pushEvent(event);
526
527         m_prevMotionTime = m_motionTime;
528
529         return true;
530 }