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