svn merge -r38814:38905 https://svn.blender.org/svnroot/bf-blender/trunk/blender .
[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
187 bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short product_id)
188         {
189         // default to NDOF_UnknownDevice so rogue button events will get discarded
190         // "mystery device" owners can help build a HID_map for their hardware
191
192         switch (vendor_id)
193                 {
194                 case 0x046D: // Logitech (3Dconnexion)
195                         switch (product_id)
196                                 {
197                                 // -- current devices --
198                                 case 0xC626:
199                                         puts("ndof: using SpaceNavigator");
200                                         m_deviceType = NDOF_SpaceNavigator;
201                                         m_buttonCount = 2;
202                                         break;
203                                 case 0xC628:
204                                         puts("ndof: using SpaceNavigator for Notebooks");
205                                         m_deviceType = NDOF_SpaceNavigator; // for Notebooks
206                                         m_buttonCount = 2;
207                                         break;
208                                 case 0xC627:
209                                         puts("ndof: using SpaceExplorer");
210                                         m_deviceType = NDOF_SpaceExplorer;
211                                         m_buttonCount = 15;
212                                         break;
213                                 case 0xC629:
214                                         puts("ndof: using SpacePilotPro");
215                                         m_deviceType = NDOF_SpacePilotPro;
216                                         m_buttonCount = 31;
217                                         break;
218
219                                 // -- older devices --
220                                 case 0xC625:
221                                         puts("ndof: using SpacePilot");
222                                         m_deviceType = NDOF_SpacePilot;
223                                         m_buttonCount = 21;
224                                         break;
225
226                                 case 0xC623:
227                                         puts("ndof: SpaceTraveler not supported, please file a bug report");
228                                         m_buttonCount = 8;
229                                         break;
230
231                                 default:
232                                         printf("ndof: unknown Logitech product %04hx\n", product_id);
233                                 }
234                         break;
235                 default:
236                         printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
237                 }
238
239         if (m_deviceType == NDOF_UnknownDevice)
240                 return false;
241         else
242                 {
243                 m_buttonMask = ~(-1 << m_buttonCount);
244
245                 #ifdef DEBUG_NDOF_BUTTONS
246                 printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask);
247                 #endif
248
249                 return true;
250                 }
251         }
252
253 void GHOST_NDOFManager::updateTranslation(short t[3], GHOST_TUns64 time)
254         {
255         memcpy(m_translation, t, sizeof(m_translation));
256         m_motionTime = time;
257         m_motionEventPending = true;
258         }
259
260 void GHOST_NDOFManager::updateRotation(short r[3], GHOST_TUns64 time)
261         {
262         memcpy(m_rotation, r, sizeof(m_rotation));
263         m_motionTime = time;
264         m_motionEventPending = true;
265         }
266
267 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
268         {
269         GHOST_EventNDOFButton* event = new GHOST_EventNDOFButton(time, window);
270         GHOST_TEventNDOFButtonData* data = (GHOST_TEventNDOFButtonData*) event->getData();
271
272         data->action = press ? GHOST_kPress : GHOST_kRelease;
273         data->button = button;
274
275         #ifdef DEBUG_NDOF_BUTTONS
276         printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released");
277         #endif
278
279         m_system.pushEvent(event);
280         }
281
282 void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
283         {
284         GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
285         GHOST_EventKey* event = new GHOST_EventKey(time, type, window, key);
286
287         #ifdef DEBUG_NDOF_BUTTONS
288         printf("keyboard %s\n", press ? "down" : "up");
289         #endif
290
291         m_system.pushEvent(event);
292         }
293
294 void GHOST_NDOFManager::updateButton(int button_number, bool press, GHOST_TUns64 time)
295         {
296         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
297
298         #ifdef DEBUG_NDOF_BUTTONS
299         if (m_deviceType != NDOF_UnknownDevice)
300                 printf("ndof: button %d -> ", button_number);
301         #endif
302
303         switch (m_deviceType)
304                 {
305                 case NDOF_SpaceNavigator:
306                         sendButtonEvent(SpaceNavigator_HID_map[button_number], press, time, window);
307                         break;
308                 case NDOF_SpaceExplorer:
309                         switch (button_number)
310                                 {
311                                 case 6: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
312                                 case 7: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
313                                 case 8: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
314                                 case 9: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
315                                 default: sendButtonEvent(SpaceExplorer_HID_map[button_number], press, time, window);
316                                 }
317                         break;
318                 case NDOF_SpacePilotPro:
319                         switch (button_number)
320                                 {
321                                 case 22: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
322                                 case 23: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
323                                 case 24: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
324                                 case 25: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
325                                 default: sendButtonEvent(SpacePilotPro_HID_map[button_number], press, time, window);
326                                 }
327                         break;
328                 case NDOF_SpacePilot:
329                         switch (button_number)
330                                 {
331                                 case 10: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
332                                 case 11: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
333                                 case 12: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
334                                 case 13: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
335                                 case 20: puts("ndof: ignoring CONFIG button"); break;
336                                 default: sendButtonEvent(SpacePilot_HID_map[button_number], press, time, window);
337                                 }
338                         break;
339                 case NDOF_UnknownDevice:
340                         printf("ndof: button %d on unknown device (ignoring)\n", button_number);
341                 }
342
343         int mask = 1 << button_number;
344         if (press)
345                 m_buttons |= mask; // set this button's bit
346         else
347                 m_buttons &= ~mask; // clear this button's bit
348         }
349
350 void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
351         {
352         button_bits &= m_buttonMask; // discard any "garbage" bits
353
354         int diff = m_buttons ^ button_bits;
355
356         for (int button_number = 0; button_number < m_buttonCount; ++button_number)
357                 {
358                 int mask = 1 << button_number;
359
360                 if (diff & mask)
361                         {
362                         bool press = button_bits & mask;
363                         updateButton(button_number, press, time);
364                         }
365                 }
366         }
367
368 void GHOST_NDOFManager::setDeadZone(float dz)
369         {
370         if (dz < 0.f)
371                 // negative values don't make sense, so clamp at zero
372                 dz = 0.f;
373         else if (dz > 0.5f)
374                 // warn the rogue user/programmer, but allow it
375                 printf("ndof: dead zone of %.2f is rather high...\n", dz);
376
377         m_deadZone = dz;
378
379         printf("ndof: dead zone set to %.2f\n", dz);
380         }
381
382 static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
383         {
384         #define HOME(foo) (ndof->foo == 0)
385         return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
386         }
387
388 static bool nearHomePosition(GHOST_TEventNDOFMotionData* ndof, float threshold)
389         {
390         if (threshold == 0.f)
391                 return atHomePosition(ndof);
392         else
393                 {
394                 #define HOME1(foo) (fabsf(ndof->foo) < threshold)
395                 return HOME1(tx) && HOME1(ty) && HOME1(tz) && HOME1(rx) && HOME1(ry) && HOME1(rz);
396                 }
397         }
398
399 bool GHOST_NDOFManager::sendMotionEvent()
400         {
401         if (!m_motionEventPending)
402                 return false;
403
404         m_motionEventPending = false; // any pending motion is handled right now
405
406         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
407         if (window == NULL)
408                 return false; // delivery will fail, so don't bother sending
409
410         GHOST_EventNDOFMotion* event = new GHOST_EventNDOFMotion(m_motionTime, window);
411         GHOST_TEventNDOFMotionData* data = (GHOST_TEventNDOFMotionData*) event->getData();
412
413         // scale axis values here to normalize them to around +/- 1
414         // they are scaled again for overall sensitivity in the WM based on user prefs
415
416         const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually
417
418         data->tx = scale * m_translation[0];
419         data->ty = scale * m_translation[1];
420         data->tz = scale * m_translation[2];
421
422         data->rx = scale * m_rotation[0];
423         data->ry = scale * m_rotation[1];
424         data->rz = scale * m_rotation[2];
425
426         data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
427
428         bool handMotion = !nearHomePosition(data, m_deadZone);
429
430         // determine what kind of motion event to send (Starting, InProgress, Finishing)
431         // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
432         switch (m_motionState)
433                 {
434                 case GHOST_kNotStarted:
435                 case GHOST_kFinished:
436                         if (handMotion)
437                                 {
438                                 data->progress = GHOST_kStarting;
439                                 m_motionState = GHOST_kInProgress;
440                                 // prev motion time will be ancient, so just make up something reasonable
441                                 data->dt = 0.0125f;
442                                 }
443                         else
444                                 {
445                                 // send no event and keep current state
446                                 delete event;
447                                 return false;
448                                 }
449                         break;
450                 case GHOST_kInProgress:
451                         if (handMotion)
452                                 {
453                                 data->progress = GHOST_kInProgress;
454                                 // keep InProgress state
455                                 }
456                         else
457                                 {
458                                 data->progress = GHOST_kFinishing;
459                                 m_motionState = GHOST_kFinished;
460                                 }
461                         break;
462                 }
463
464         #ifdef DEBUG_NDOF_MOTION
465         printf("ndof motion sent -- %s\n", progress_string[data->progress]);
466
467         // show details about this motion event
468         printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
469                 data->tx, data->ty, data->tz,
470                 data->rx, data->ry, data->rz,
471                 data->dt);
472         #endif
473
474         m_system.pushEvent(event);
475
476         m_prevMotionTime = m_motionTime;
477
478         return true;
479         }