Cleanup: remove contributors for CMake files
[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 };
85 #endif
86
87 // shared by the latest 3Dconnexion hardware
88 // SpacePilotPro uses all of these
89 // smaller devices use only some, based on button mask
90 static const NDOF_ButtonT Modern3Dx_HID_map[] = {
91         NDOF_BUTTON_MENU,
92         NDOF_BUTTON_FIT,
93         NDOF_BUTTON_TOP,
94         NDOF_BUTTON_LEFT,
95         NDOF_BUTTON_RIGHT,
96         NDOF_BUTTON_FRONT,
97         NDOF_BUTTON_BOTTOM,
98         NDOF_BUTTON_BACK,
99         NDOF_BUTTON_ROLL_CW,
100         NDOF_BUTTON_ROLL_CCW,
101         NDOF_BUTTON_ISO1,
102         NDOF_BUTTON_ISO2,
103         NDOF_BUTTON_1,
104         NDOF_BUTTON_2,
105         NDOF_BUTTON_3,
106         NDOF_BUTTON_4,
107         NDOF_BUTTON_5,
108         NDOF_BUTTON_6,
109         NDOF_BUTTON_7,
110         NDOF_BUTTON_8,
111         NDOF_BUTTON_9,
112         NDOF_BUTTON_10,
113         NDOF_BUTTON_ESC,
114         NDOF_BUTTON_ALT,
115         NDOF_BUTTON_SHIFT,
116         NDOF_BUTTON_CTRL,
117         NDOF_BUTTON_ROTATE,
118         NDOF_BUTTON_PANZOOM,
119         NDOF_BUTTON_DOMINANT,
120         NDOF_BUTTON_PLUS,
121         NDOF_BUTTON_MINUS
122 };
123
124 static const NDOF_ButtonT SpaceExplorer_HID_map[] = {
125         NDOF_BUTTON_1,
126         NDOF_BUTTON_2,
127         NDOF_BUTTON_TOP,
128         NDOF_BUTTON_LEFT,
129         NDOF_BUTTON_RIGHT,
130         NDOF_BUTTON_FRONT,
131         NDOF_BUTTON_ESC,
132         NDOF_BUTTON_ALT,
133         NDOF_BUTTON_SHIFT,
134         NDOF_BUTTON_CTRL,
135         NDOF_BUTTON_FIT,
136         NDOF_BUTTON_MENU,
137         NDOF_BUTTON_PLUS,
138         NDOF_BUTTON_MINUS,
139         NDOF_BUTTON_ROTATE
140 };
141
142 // this is the older SpacePilot (sans Pro)
143 // thanks to polosson for info about this device
144 static const NDOF_ButtonT SpacePilot_HID_map[] = {
145         NDOF_BUTTON_1,
146         NDOF_BUTTON_2,
147         NDOF_BUTTON_3,
148         NDOF_BUTTON_4,
149         NDOF_BUTTON_5,
150         NDOF_BUTTON_6,
151         NDOF_BUTTON_TOP,
152         NDOF_BUTTON_LEFT,
153         NDOF_BUTTON_RIGHT,
154         NDOF_BUTTON_FRONT,
155         NDOF_BUTTON_ESC,
156         NDOF_BUTTON_ALT,
157         NDOF_BUTTON_SHIFT,
158         NDOF_BUTTON_CTRL,
159         NDOF_BUTTON_FIT,
160         NDOF_BUTTON_MENU,
161         NDOF_BUTTON_PLUS,
162         NDOF_BUTTON_MINUS,
163         NDOF_BUTTON_DOMINANT,
164         NDOF_BUTTON_ROTATE,
165         NDOF_BUTTON_NONE // the CONFIG button -- what does it do?
166 };
167
168 static const NDOF_ButtonT Generic_HID_map[] = {
169         NDOF_BUTTON_1,
170         NDOF_BUTTON_2,
171         NDOF_BUTTON_3,
172         NDOF_BUTTON_4,
173         NDOF_BUTTON_5,
174         NDOF_BUTTON_6,
175         NDOF_BUTTON_7,
176         NDOF_BUTTON_8,
177         NDOF_BUTTON_9,
178         NDOF_BUTTON_A,
179         NDOF_BUTTON_B,
180         NDOF_BUTTON_C
181 };
182
183 static const int genericButtonCount = sizeof(Generic_HID_map) / sizeof(NDOF_ButtonT);
184
185 GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System &sys)
186         : m_system(sys),
187           m_deviceType(NDOF_UnknownDevice), // each platform has its own device detection code
188           m_buttonCount(genericButtonCount),
189           m_buttonMask(0),
190           m_hidMap(Generic_HID_map),
191           m_buttons(0),
192           m_motionTime(0),
193           m_prevMotionTime(0),
194           m_motionState(GHOST_kNotStarted),
195           m_motionEventPending(false),
196           m_deadZone(0.0f)
197 {
198         // to avoid the rare situation where one triple is updated and
199         // the other is not, initialize them both here:
200         memset(m_translation, 0, sizeof(m_translation));
201         memset(m_rotation, 0, sizeof(m_rotation));
202 }
203
204 bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short product_id)
205 {
206         // call this function until it returns true
207         // it's a good idea to stop calling it after that, as it will "forget"
208         // whichever device it already found
209
210         // default to safe generic behavior for "unknown" devices
211         // unidentified devices will emit motion events like normal
212         // rogue buttons do nothing by default, but can be customized by the user
213
214         m_deviceType = NDOF_UnknownDevice;
215         m_hidMap = Generic_HID_map;
216         m_buttonCount = genericButtonCount;
217         m_buttonMask = 0;
218
219         // "mystery device" owners can help build a HID_map for their hardware
220         // A few users have already contributed information about several older devices
221         // that I don't have access to. Thanks!
222
223         switch (vendor_id) {
224                 case 0x046D: // Logitech (3Dconnexion was a subsidiary)
225                         switch (product_id) {
226                                 // -- current devices --
227                                 case 0xC626: // full-size SpaceNavigator
228                                 case 0xC628: // the "for Notebooks" one
229                                         puts("ndof: using SpaceNavigator");
230                                         m_deviceType = NDOF_SpaceNavigator;
231                                         m_buttonCount = 2;
232                                         m_hidMap = Modern3Dx_HID_map;
233                                         break;
234                                 case 0xC627:
235                                         puts("ndof: using SpaceExplorer");
236                                         m_deviceType = NDOF_SpaceExplorer;
237                                         m_buttonCount = 15;
238                                         m_hidMap = SpaceExplorer_HID_map;
239                                         break;
240                                 case 0xC629:
241                                         puts("ndof: using SpacePilot Pro");
242                                         m_deviceType = NDOF_SpacePilotPro;
243                                         m_buttonCount = 31;
244                                         m_hidMap = Modern3Dx_HID_map;
245                                         break;
246                                 case 0xC62B:
247                                         puts("ndof: using SpaceMouse Pro");
248                                         m_deviceType = NDOF_SpaceMousePro;
249                                         m_buttonCount = 27;
250                                         // ^^ actually has 15 buttons, but their HID codes range from 0 to 26
251                                         m_buttonMask = 0x07C0F137;
252                                         m_hidMap = Modern3Dx_HID_map;
253                                         break;
254
255                                 // -- older devices --
256                                 case 0xC625:
257                                         puts("ndof: using SpacePilot");
258                                         m_deviceType = NDOF_SpacePilot;
259                                         m_buttonCount = 21;
260                                         m_hidMap = SpacePilot_HID_map;
261                                         break;
262                                 case 0xC621:
263                                         puts("ndof: using Spaceball 5000");
264                                         m_deviceType = NDOF_Spaceball5000;
265                                         m_buttonCount = 12;
266                                         break;
267                                 case 0xC623:
268                                         puts("ndof: using SpaceTraveler");
269                                         m_deviceType = NDOF_SpaceTraveler;
270                                         m_buttonCount = 8;
271                                         break;
272
273                                 default:
274                                         printf("ndof: unknown Logitech product %04hx\n", product_id);
275                         }
276                         break;
277                 case 0x256F: // 3Dconnexion
278                         switch (product_id) {
279                                 case 0xC62E: // plugged in
280                                 case 0xC62F: // wireless
281                                         puts("ndof: using SpaceMouse Wireless");
282                                         m_deviceType = NDOF_SpaceMouseWireless;
283                                         m_buttonCount = 2;
284                                         m_hidMap = Modern3Dx_HID_map;
285                                         break;
286                                 case 0xC631: // plugged in
287                                 case 0xC632: // wireless
288                                         puts("ndof: using SpaceMouse Pro Wireless");
289                                         m_deviceType = NDOF_SpaceMouseProWireless;
290                                         m_buttonCount = 27;
291                                         // ^^ actually has 15 buttons, but their HID codes range from 0 to 26
292                                         m_buttonMask = 0x07C0F137;
293                                         m_hidMap = Modern3Dx_HID_map;
294                                         break;
295                                 case 0xC633:
296                                         puts("ndof: using SpaceMouse Enterprise");
297                                         m_deviceType = NDOF_SpaceMouseEnterprise;
298                                         m_buttonCount = 31;
299                                         m_hidMap = Modern3Dx_HID_map;
300                                         break;
301
302                                 default:
303                                         printf("ndof: unknown 3Dconnexion product %04hx\n", product_id);
304                         }
305                         break;
306                 default:
307                         printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
308         }
309
310         if (m_buttonMask == 0)
311                 m_buttonMask = (int) ~(UINT_MAX << m_buttonCount);
312
313 #ifdef DEBUG_NDOF_BUTTONS
314         printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask);
315 #endif
316
317         return m_deviceType != NDOF_UnknownDevice;
318 }
319
320 void GHOST_NDOFManager::updateTranslation(const int t[3], GHOST_TUns64 time)
321 {
322         memcpy(m_translation, t, sizeof(m_translation));
323         m_motionTime = time;
324         m_motionEventPending = true;
325 }
326
327 void GHOST_NDOFManager::updateRotation(const int r[3], GHOST_TUns64 time)
328 {
329         memcpy(m_rotation, r, sizeof(m_rotation));
330         m_motionTime = time;
331         m_motionEventPending = true;
332 }
333
334 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow *window)
335 {
336         GHOST_ASSERT(button > NDOF_BUTTON_NONE && button < NDOF_BUTTON_LAST,
337                      "rogue button trying to escape NDOF manager");
338
339         GHOST_EventNDOFButton *event = new GHOST_EventNDOFButton(time, window);
340         GHOST_TEventNDOFButtonData *data = (GHOST_TEventNDOFButtonData *) event->getData();
341
342         data->action = press ? GHOST_kPress : GHOST_kRelease;
343         data->button = button;
344
345 #ifdef DEBUG_NDOF_BUTTONS
346         printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released");
347 #endif
348
349         m_system.pushEvent(event);
350 }
351
352 void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, bool press, GHOST_TUns64 time, GHOST_IWindow *window)
353 {
354         GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
355         GHOST_EventKey *event = new GHOST_EventKey(time, type, window, key);
356
357 #ifdef DEBUG_NDOF_BUTTONS
358         printf("keyboard %s\n", press ? "down" : "up");
359 #endif
360
361         m_system.pushEvent(event);
362 }
363
364 void GHOST_NDOFManager::updateButton(int button_number, bool press, GHOST_TUns64 time)
365 {
366         GHOST_IWindow *window = m_system.getWindowManager()->getActiveWindow();
367
368 #ifdef DEBUG_NDOF_BUTTONS
369         printf("ndof: button %d -> ", button_number);
370 #endif
371
372         NDOF_ButtonT button = (button_number < m_buttonCount) ? m_hidMap[button_number] : NDOF_BUTTON_NONE;
373
374         switch (button) {
375                 case NDOF_BUTTON_NONE:
376 #ifdef DEBUG_NDOF_BUTTONS
377                         printf("discarded\n");
378 #endif
379                         break;
380                 case NDOF_BUTTON_ESC: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
381                 case NDOF_BUTTON_ALT: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
382                 case NDOF_BUTTON_SHIFT: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
383                 case NDOF_BUTTON_CTRL: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
384                 default: sendButtonEvent(button, press, time, window);
385         }
386
387         int mask = 1 << button_number;
388         if (press) {
389                 m_buttons |= mask; // set this button's bit
390         }
391         else {
392                 m_buttons &= ~mask; // clear this button's bit
393         }
394 }
395
396 void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
397 {
398         button_bits &= m_buttonMask; // discard any "garbage" bits
399
400         int diff = m_buttons ^ button_bits;
401
402         for (int button_number = 0; button_number < m_buttonCount; ++button_number) {
403                 int mask = 1 << button_number;
404
405                 if (diff & mask) {
406                         bool press = button_bits & mask;
407                         updateButton(button_number, press, time);
408                 }
409         }
410 }
411
412 void GHOST_NDOFManager::setDeadZone(float dz)
413 {
414         if (dz < 0.0f) {
415                 // negative values don't make sense, so clamp at zero
416                 dz = 0.0f;
417         }
418         else if (dz > 0.5f) {
419                 // warn the rogue user/developer, but allow it
420                 GHOST_PRINTF("ndof: dead zone of %.2f is rather high...\n", dz);
421         }
422         m_deadZone = dz;
423
424         GHOST_PRINTF("ndof: dead zone set to %.2f\n", dz);
425 }
426
427 static bool atHomePosition(GHOST_TEventNDOFMotionData *ndof)
428 {
429 #define HOME(foo) (ndof->foo == 0.0f)
430         return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
431 #undef HOME
432 }
433
434 static bool nearHomePosition(GHOST_TEventNDOFMotionData *ndof, float threshold)
435 {
436         if (threshold == 0.0f) {
437                 return atHomePosition(ndof);
438         }
439         else {
440 #define HOME(foo) (fabsf(ndof->foo) < threshold)
441                 return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
442 #undef HOME
443         }
444 }
445
446 bool GHOST_NDOFManager::sendMotionEvent()
447 {
448         if (!m_motionEventPending)
449                 return false;
450
451         m_motionEventPending = false; // any pending motion is handled right now
452
453         GHOST_IWindow *window = m_system.getWindowManager()->getActiveWindow();
454
455         if (window == NULL) {
456                 m_motionState = GHOST_kNotStarted; // avoid large 'dt' times when changing windows
457                 return false; // delivery will fail, so don't bother sending
458         }
459
460         GHOST_EventNDOFMotion *event = new GHOST_EventNDOFMotion(m_motionTime, window);
461         GHOST_TEventNDOFMotionData *data = (GHOST_TEventNDOFMotionData *) event->getData();
462
463         // scale axis values here to normalize them to around +/- 1
464         // they are scaled again for overall sensitivity in the WM based on user prefs
465
466         const float scale = 1.0f / 350.0f; // 3Dconnexion devices send +/- 350 usually
467
468         data->tx = scale * m_translation[0];
469         data->ty = scale * m_translation[1];
470         data->tz = scale * m_translation[2];
471
472         data->rx = scale * m_rotation[0];
473         data->ry = scale * m_rotation[1];
474         data->rz = scale * m_rotation[2];
475
476         data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
477         m_prevMotionTime = m_motionTime;
478
479         bool weHaveMotion = !nearHomePosition(data, m_deadZone);
480
481         // determine what kind of motion event to send (Starting, InProgress, Finishing)
482         // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
483         switch (m_motionState) {
484                 case GHOST_kNotStarted:
485                 case GHOST_kFinished:
486                         if (weHaveMotion) {
487                                 data->progress = GHOST_kStarting;
488                                 m_motionState = GHOST_kInProgress;
489                                 // prev motion time will be ancient, so just make up a reasonable time delta
490                                 data->dt = 0.0125f;
491                         }
492                         else {
493                                 // send no event and keep current state
494 #ifdef DEBUG_NDOF_MOTION
495                                 printf("ndof motion ignored -- %s\n", progress_string[data->progress]);
496 #endif
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=(%d,%d,%d) R=(%d,%d,%d) raw\n",
520                m_translation[0], m_translation[1], m_translation[2],
521                m_rotation[0], m_rotation[1], m_rotation[2]);
522         printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
523                data->tx, data->ty, data->tz,
524                data->rx, data->ry, data->rz,
525                data->dt);
526 #endif
527
528         m_system.pushEvent(event);
529
530         return true;
531 }