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