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