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