69100fefd31945ef6b038a787f51080f4c903506
[blender.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 GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System& sys)
143         : m_system(sys)
144         , m_deviceType(NDOF_UnknownDevice) // each platform has its own device detection code
145         , m_buttonCount(0)
146         , m_buttonMask(0)
147         , m_buttons(0)
148         , m_motionTime(0)
149         , m_prevMotionTime(0)
150         , m_motionState(GHOST_kNotStarted)
151         , m_motionEventPending(false)
152         , m_deadZone(0.f)
153         {
154         // to avoid the rare situation where one triple is updated and
155         // the other is not, initialize them both here:
156         memset(m_translation, 0, sizeof(m_translation));
157         memset(m_rotation, 0, sizeof(m_rotation));
158         }
159
160 bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short product_id)
161         {
162         switch (vendor_id)
163                 {
164                 case 0x046D: // Logitech (3Dconnexion)
165                         switch (product_id)
166                                 {
167                                 // -- current devices --
168                                 case 0xC626:
169                                         puts("ndof: using SpaceNavigator");
170                                         m_deviceType = NDOF_SpaceNavigator;
171                                         m_buttonCount = 2;
172                                         break;
173                                 case 0xC628:
174                                         puts("ndof: using SpaceNavigator for Notebooks");
175                                         m_deviceType = NDOF_SpaceNavigator; // for Notebooks
176                                         m_buttonCount = 2;
177                                         break;
178                                 case 0xC627:
179                                         puts("ndof: using SpaceExplorer");
180                                         m_deviceType = NDOF_SpaceExplorer;
181                                         m_buttonCount = 15;
182                                         break;
183                                 case 0xC629:
184                                         puts("ndof: using SpacePilotPro");
185                                         m_deviceType = NDOF_SpacePilotPro;
186                                         m_buttonCount = 31;
187                                         break;
188
189                                 // -- older devices --
190                                 // keep unknown device type so rogue button events will get discarded
191                                 // "mystery device" owners can help build another HID_map for their hardware
192                                 case 0xC623:
193                                         puts("ndof: SpaceTraveler not supported, please file a bug report");
194                                         m_buttonCount = 8;
195                                         break;
196                                 case 0xC625:
197                                         puts("ndof: SpacePilot not supported, please file a bug report");
198                                         m_buttonCount = 21;
199                                         break;
200
201                                 default:
202                                         printf("ndof: unknown Logitech product %04hx\n", product_id);
203                                 }
204                         break;
205                 default:
206                         printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id);
207                 }
208
209         if (m_deviceType == NDOF_UnknownDevice)
210                 return false;
211         else
212                 {
213                 m_buttonMask = ~(-1 << m_buttonCount);
214
215                 #ifdef DEBUG_NDOF_BUTTONS
216                 printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask);
217                 #endif
218
219                 return true;
220                 }
221         }
222
223 void GHOST_NDOFManager::updateTranslation(short t[3], GHOST_TUns64 time)
224         {
225         memcpy(m_translation, t, sizeof(m_translation));
226         m_motionTime = time;
227         m_motionEventPending = true;
228         }
229
230 void GHOST_NDOFManager::updateRotation(short r[3], GHOST_TUns64 time)
231         {
232         memcpy(m_rotation, r, sizeof(m_rotation));
233         m_motionTime = time;
234         m_motionEventPending = true;
235         }
236
237 void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
238         {
239         GHOST_EventNDOFButton* event = new GHOST_EventNDOFButton(time, window);
240         GHOST_TEventNDOFButtonData* data = (GHOST_TEventNDOFButtonData*) event->getData();
241
242         data->action = press ? GHOST_kPress : GHOST_kRelease;
243         data->button = button;
244
245         #ifdef DEBUG_NDOF_BUTTONS
246         printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released");
247         #endif
248
249         m_system.pushEvent(event);
250         }
251
252 void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, bool press, GHOST_TUns64 time, GHOST_IWindow* window)
253         {
254         GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
255         GHOST_EventKey* event = new GHOST_EventKey(time, type, window, key);
256
257         #ifdef DEBUG_NDOF_BUTTONS
258         printf("keyboard %s\n", press ? "down" : "up");
259         #endif
260
261         m_system.pushEvent(event);
262         }
263
264 void GHOST_NDOFManager::updateButton(int button_number, bool press, GHOST_TUns64 time)
265         {
266         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
267
268         #ifdef DEBUG_NDOF_BUTTONS
269         if (m_deviceType != NDOF_UnknownDevice)
270                 printf("ndof: button %d -> ", button_number);
271         #endif
272
273         switch (m_deviceType)
274                 {
275                 case NDOF_SpaceNavigator:
276                         sendButtonEvent(SpaceNavigator_HID_map[button_number], press, time, window);
277                         break;
278                 case NDOF_SpaceExplorer:
279                         switch (button_number)
280                                 {
281                                 case 6: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
282                                 case 7: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
283                                 case 8: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
284                                 case 9: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
285                                 default: sendButtonEvent(SpaceExplorer_HID_map[button_number], press, time, window);
286                                 }
287                         break;
288                 case NDOF_SpacePilotPro:
289                         switch (button_number)
290                                 {
291                                 case 22: sendKeyEvent(GHOST_kKeyEsc, press, time, window); break;
292                                 case 23: sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); break;
293                                 case 24: sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); break;
294                                 case 25: sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); break;
295                                 default: sendButtonEvent(SpacePilotPro_HID_map[button_number], press, time, window);
296                                 }
297                         break;
298                 case NDOF_UnknownDevice:
299                         printf("ndof: button %d on unknown device (ignoring)\n", button_number);
300                 }
301
302         int mask = 1 << button_number;
303         if (press)
304                 m_buttons |= mask; // set this button's bit
305         else
306                 m_buttons &= ~mask; // clear this button's bit
307         }
308
309 void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time)
310         {
311         button_bits &= m_buttonMask; // discard any "garbage" bits
312
313         int diff = m_buttons ^ button_bits;
314
315         for (int button_number = 0; button_number < m_buttonCount; ++button_number)
316                 {
317                 int mask = 1 << button_number;
318
319                 if (diff & mask)
320                         {
321                         bool press = button_bits & mask;
322                         updateButton(button_number, press, time);
323                         }
324                 }
325         }
326
327 void GHOST_NDOFManager::setDeadZone(float dz)
328         {
329         if (dz < 0.f)
330                 // negative values don't make sense, so clamp at zero
331                 dz = 0.f;
332         else if (dz > 0.5f)
333                 // warn the rogue user/programmer, but allow it
334                 printf("ndof: dead zone of %.2f is rather high...\n", dz);
335
336         m_deadZone = dz;
337
338         printf("ndof: dead zone set to %.2f\n", dz);
339         }
340
341 static bool atHomePosition(GHOST_TEventNDOFMotionData* ndof)
342         {
343         #define HOME(foo) (ndof->foo == 0)
344         return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
345         }
346
347 static bool nearHomePosition(GHOST_TEventNDOFMotionData* ndof, float threshold)
348         {
349         if (threshold == 0.f)
350                 return atHomePosition(ndof);
351         else
352                 {
353                 #define HOME1(foo) (fabsf(ndof->foo) < threshold)
354                 return HOME1(tx) && HOME1(ty) && HOME1(tz) && HOME1(rx) && HOME1(ry) && HOME1(rz);
355                 }
356         }
357
358 bool GHOST_NDOFManager::sendMotionEvent()
359         {
360         if (!m_motionEventPending)
361                 return false;
362
363         m_motionEventPending = false; // any pending motion is handled right now
364
365         GHOST_IWindow* window = m_system.getWindowManager()->getActiveWindow();
366         if (window == NULL)
367                 return false; // delivery will fail, so don't bother sending
368
369         GHOST_EventNDOFMotion* event = new GHOST_EventNDOFMotion(m_motionTime, window);
370         GHOST_TEventNDOFMotionData* data = (GHOST_TEventNDOFMotionData*) event->getData();
371
372         const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually
373
374         // probable future enhancement
375         // scale *= U.ndof_sensitivity;
376
377         data->tx = scale * m_translation[0];
378         data->ty = scale * m_translation[1];
379         data->tz = scale * m_translation[2];
380
381         data->rx = scale * m_rotation[0];
382         data->ry = scale * m_rotation[1];
383         data->rz = scale * m_rotation[2];
384
385         data->dt = 0.001f * (m_motionTime - m_prevMotionTime); // in seconds
386
387         bool handMotion = !nearHomePosition(data, m_deadZone);
388
389         // determine what kind of motion event to send (Starting, InProgress, Finishing)
390         // and where that leaves this NDOF manager (NotStarted, InProgress, Finished)
391         switch (m_motionState)
392                 {
393                 case GHOST_kNotStarted:
394                 case GHOST_kFinished:
395                         if (handMotion)
396                                 {
397                                 data->progress = GHOST_kStarting;
398                                 m_motionState = GHOST_kInProgress;
399                                 // prev motion time will be ancient, so just make up something reasonable
400                                 data->dt = 0.0125f;
401                                 }
402                         else
403                                 {
404                                 // send no event and keep current state
405                                 delete event;
406                                 return false;
407                                 }
408                         break;
409                 case GHOST_kInProgress:
410                         if (handMotion)
411                                 {
412                                 data->progress = GHOST_kInProgress;
413                                 // keep InProgress state
414                                 }
415                         else
416                                 {
417                                 data->progress = GHOST_kFinishing;
418                                 m_motionState = GHOST_kFinished;
419                                 }
420                         break;
421                 }
422
423         #ifdef DEBUG_NDOF_MOTION
424         printf("ndof motion sent -- %s\n", progress_string[data->progress]);
425
426         // show details about this motion event
427         printf("    T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n",
428                 data->tx, data->ty, data->tz,
429                 data->rx, data->ry, data->rz,
430                 data->dt);
431         #endif
432
433         m_system.pushEvent(event);
434
435         m_prevMotionTime = m_motionTime;
436
437         return true;
438         }