1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2013-2015 SuperTuxKart-Team
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License: or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 #include <cassert>
20 #include <algorithm>
21 
22 #include "config/user_config.hpp"
23 #include "input/multitouch_device.hpp"
24 #include "karts/abstract_kart.hpp"
25 #include "karts/controller/controller.hpp"
26 #include "graphics/irr_driver.hpp"
27 #include "guiengine/modaldialog.hpp"
28 #include "guiengine/screen_keyboard.hpp"
29 
30 // ----------------------------------------------------------------------------
31 /** The multitouch device constructor
32  */
MultitouchDevice()33 MultitouchDevice::MultitouchDevice()
34 {
35     m_configuration = NULL;
36     m_type          = DT_MULTITOUCH;
37     m_name          = "Multitouch";
38     m_player        = NULL;
39     m_controller    = NULL;
40     m_irrlicht_device = irr_driver->getDevice();
41 
42     reset();
43     updateConfigParams();
44 }   // MultitouchDevice
45 
46 // ----------------------------------------------------------------------------
47 /** The multitouch device destructor
48  */
~MultitouchDevice()49 MultitouchDevice::~MultitouchDevice()
50 {
51     clearButtons();
52 }
53 
54 // ----------------------------------------------------------------------------
55 /** Returns a number of fingers that are currently in use
56  */
getActiveTouchesCount()57 unsigned int MultitouchDevice::getActiveTouchesCount()
58 {
59     unsigned int count = 0;
60 
61     for (MultitouchEvent event : m_events)
62     {
63         if (event.touched)
64             count++;
65     }
66 
67     return count;
68 } // getActiveTouchesCount
69 
70 // ----------------------------------------------------------------------------
71 /** Creates a button of specified type and position. The button is then updated
72  *  when touch event occurs and proper action is sent to player controller.
73  *  Note that it just determines the screen area that is considered as button
74  *  and it doesn't draw the GUI element on a screen.
75  *  \param type The button type that determines its behaviour.
76  *  \param x Vertical position of the button.
77  *  \param y Horizontal position of the button.
78  *  \param width Width of the button.
79  *  \param height Height of the button.
80  *  \param callback Pointer to a function that is executed on button event.
81  */
addButton(MultitouchButtonType type,int x,int y,int width,int height,void (* callback)(unsigned int,bool))82 void MultitouchDevice::addButton(MultitouchButtonType type, int x, int y,
83                                  int width, int height,
84                                  void (*callback)(unsigned int, bool))
85 {
86     assert(width > 0 && height > 0);
87 
88     MultitouchButton* button = new MultitouchButton();
89     button->type = type;
90     button->event_id = 0;
91     button->pressed = false;
92     button->x = x;
93     button->y = y;
94     button->width = width;
95     button->height = height;
96     button->axis_x = 0.0f;
97     button->axis_y = 0.0f;
98     button->id = m_buttons.size();
99     button->callback = callback;
100 
101     switch (button->type)
102     {
103     case MultitouchButtonType::BUTTON_FIRE:
104         button->action = PA_FIRE;
105         break;
106     case MultitouchButtonType::BUTTON_NITRO:
107         button->action = PA_NITRO;
108         break;
109     case MultitouchButtonType::BUTTON_SKIDDING:
110         button->action = PA_DRIFT;
111         break;
112     case MultitouchButtonType::BUTTON_LOOK_BACKWARDS:
113         button->action = PA_LOOK_BACK;
114         break;
115     case MultitouchButtonType::BUTTON_RESCUE:
116         button->action = PA_RESCUE;
117         break;
118     case MultitouchButtonType::BUTTON_ESCAPE:
119         button->action = PA_PAUSE_RACE;
120         break;
121     case MultitouchButtonType::BUTTON_UP:
122         button->action = PA_ACCEL;
123         break;
124     case MultitouchButtonType::BUTTON_DOWN:
125         button->action = PA_BRAKE;
126         break;
127     case MultitouchButtonType::BUTTON_LEFT:
128         button->action = PA_STEER_LEFT;
129         break;
130     case MultitouchButtonType::BUTTON_RIGHT:
131         button->action = PA_STEER_RIGHT;
132         break;
133     default:
134         button->action = PA_BEFORE_FIRST;
135         break;
136     }
137 
138     m_buttons.push_back(button);
139 } // addButton
140 
141 // ----------------------------------------------------------------------------
142 /** Deletes all previously created buttons
143  */
clearButtons()144 void MultitouchDevice::clearButtons()
145 {
146     for (MultitouchButton* button : m_buttons)
147     {
148         delete button;
149     }
150 
151     m_buttons.clear();
152 } // clearButtons
153 
154 // ----------------------------------------------------------------------------
155 /** Sets all buttons and events to default state
156  */
reset()157 void MultitouchDevice::reset()
158 {
159     for (MultitouchButton* button : m_buttons)
160     {
161         button->pressed = false;
162         button->event_id = 0;
163         button->axis_x = 0.0f;
164         button->axis_y = 0.0f;
165     }
166 
167     for (MultitouchEvent& event : m_events)
168     {
169         event.id = 0;
170         event.touched = false;
171         event.x = 0;
172         event.y = 0;
173     }
174 
175     m_orientation = 0.0f;
176     m_gyro_time = 0;
177     m_affected_linked_buttons.clear();
178 } // reset
179 
180 // ----------------------------------------------------------------------------
181 /** Activates accelerometer
182  */
activateAccelerometer()183 void MultitouchDevice::activateAccelerometer()
184 {
185     if (!m_irrlicht_device->isAccelerometerActive())
186     {
187         m_irrlicht_device->activateAccelerometer(1.0f / 30);
188     }
189 }
190 
191 // ----------------------------------------------------------------------------
192 /** Deativates accelerometer
193  */
deactivateAccelerometer()194 void MultitouchDevice::deactivateAccelerometer()
195 {
196     if (m_irrlicht_device->isAccelerometerActive())
197     {
198         m_irrlicht_device->deactivateAccelerometer();
199     }
200 }
201 
202 // ----------------------------------------------------------------------------
203 /** Get accelerometer state
204  *  \return true if accelerometer is active
205  */
isAccelerometerActive()206 bool MultitouchDevice::isAccelerometerActive()
207 {
208     return m_irrlicht_device->isAccelerometerActive();
209 }
210 
211 // ----------------------------------------------------------------------------
212 /** Activates gyroscope
213  */
activateGyroscope()214 void MultitouchDevice::activateGyroscope()
215 {
216     if (!m_irrlicht_device->isGyroscopeActive())
217     {
218         // Assume 60 FPS, some phones can do 90 and 120 FPS but we won't handle
219         // them now
220         m_irrlicht_device->activateGyroscope(1.0f / 60);
221     }
222 }
223 
224 // ----------------------------------------------------------------------------
225 /** Deativates gyroscope
226  */
deactivateGyroscope()227 void MultitouchDevice::deactivateGyroscope()
228 {
229     if (m_irrlicht_device->isGyroscopeActive())
230     {
231         m_irrlicht_device->deactivateGyroscope();
232     }
233 }
234 
235 // ----------------------------------------------------------------------------
236 /** Get gyroscope state
237  *  \return true if gyroscope is active
238  */
isGyroscopeActive()239 bool MultitouchDevice::isGyroscopeActive()
240 {
241     return m_irrlicht_device->isGyroscopeActive();
242 }
243 
244 // ----------------------------------------------------------------------------
245 /** The function that is executed when touch event occurs. It updates the
246  *  buttons state when it's needed.
247  *  \param event_id The id of touch event that should be processed.
248  */
updateDeviceState(unsigned int event_id)249 void MultitouchDevice::updateDeviceState(unsigned int event_id)
250 {
251     assert(event_id < m_events.size());
252 
253     auto inside_button = [](MultitouchButton* button,
254         const MultitouchEvent& event)->bool
255         {
256             if (button == NULL)
257                 return false;
258             return event.x >= button->x &&
259                 event.x <= button->x + button->width &&
260                 event.y >= button->y && event.y <= button->y + button->height;
261         };
262     auto is_linked_button = [](MultitouchButton* button)->bool
263         {
264             if (button == NULL)
265                 return false;
266             return button->type >= BUTTON_FIRE &&
267                 button->type <= BUTTON_RESCUE;
268         };
269 
270     const MultitouchEvent& event = m_events[event_id];
271     std::vector<MultitouchButton*> linked_buttons;
272     for (MultitouchButton* button : m_buttons)
273     {
274         if (button == NULL || !is_linked_button(button))
275             continue;
276         linked_buttons.push_back(button);
277     }
278 
279     if (!event.touched)
280     {
281         m_affected_linked_buttons.erase(event.id);
282     }
283     else
284     {
285         for (MultitouchButton* button : linked_buttons)
286         {
287             if (inside_button(button, event))
288             {
289                 auto& ret = m_affected_linked_buttons[event.id];
290                 if (!ret.empty() && ret.back() == button)
291                     continue;
292                 if (ret.size() >= 2)
293                 {
294                     if (ret[0] == button)
295                         ret.resize(1);
296                     else if (ret[ret.size() - 2] == button)
297                         ret.resize(ret.size() - 1);
298                     else
299                         ret.push_back(button);
300                 }
301                 else
302                     ret.push_back(button);
303                 break;
304             }
305         }
306     }
307     for (MultitouchButton* button : linked_buttons)
308     {
309         bool found = false;
310         for (auto& p : m_affected_linked_buttons)
311         {
312             auto it = std::find(p.second.begin(), p.second.end(), button);
313             if (it != p.second.end())
314             {
315                 button->pressed = true;
316                 handleControls(button);
317                 found = true;
318                 break;
319             }
320         }
321         if (!found)
322         {
323             button->pressed = false;
324             handleControls(button);
325         }
326     }
327 
328     MultitouchButton* pressed_button = NULL;
329 
330     for (MultitouchButton* button : m_buttons)
331     {
332         if (is_linked_button(button))
333             continue;
334         if (button->pressed && button->event_id == event_id)
335         {
336             pressed_button = button;
337             break;
338         }
339     }
340 
341     for (MultitouchButton* button : m_buttons)
342     {
343         if (is_linked_button(button))
344             continue;
345         if (pressed_button != NULL && button != pressed_button)
346             continue;
347 
348         bool update_controls = false;
349         bool prev_button_state = button->pressed;
350 
351         if (pressed_button != NULL || inside_button(button, event))
352         {
353             button->pressed = event.touched;
354             button->event_id = event_id;
355 
356             if (button->type == MultitouchButtonType::BUTTON_STEERING)
357             {
358                 float prev_axis_x = button->axis_x;
359 
360                 if (button->pressed == true)
361                 {
362                     button->axis_x =
363                         (float)(event.x - button->x) / (button->width/2) - 1;
364                 }
365                 else
366                 {
367                     button->axis_x = 0.0f;
368                 }
369 
370                 if (prev_axis_x != button->axis_x)
371                 {
372                     update_controls = true;
373                 }
374             }
375 
376             if (button->type == MultitouchButtonType::BUTTON_STEERING ||
377                 button->type == MultitouchButtonType::BUTTON_UP_DOWN)
378             {
379                 float prev_axis_y = button->axis_y;
380 
381                 if (button->pressed == true)
382                 {
383                     button->axis_y =
384                         (float)(event.y - button->y) / (button->height/2) - 1;
385                 }
386                 else
387                 {
388                     button->axis_y = 0.0f;
389                 }
390 
391                 if (prev_axis_y != button->axis_y)
392                 {
393                     update_controls = true;
394                 }
395             }
396         }
397 
398         if (prev_button_state != button->pressed)
399         {
400             update_controls = true;
401         }
402 
403         if (button->type == MultitouchButtonType::BUTTON_UP_DOWN &&
404             !button->pressed)
405         {
406             update_controls = false;
407         }
408 
409         if (update_controls)
410         {
411             handleControls(button);
412         }
413 
414     }
415 } // updateDeviceState
416 
417 // ----------------------------------------------------------------------------
418 /** Updates config parameters i.e. when they are modified in options
419  */
updateConfigParams()420 void MultitouchDevice::updateConfigParams()
421 {
422     m_deadzone = UserConfigParams::m_multitouch_deadzone;
423     m_deadzone = std::min(std::max(m_deadzone, 0.0f), 0.5f);
424 
425     m_sensitivity_x = UserConfigParams::m_multitouch_sensitivity_x;
426     m_sensitivity_x = std::min(std::max(m_sensitivity_x, 0.0f), 1.0f);
427 
428     m_sensitivity_y = UserConfigParams::m_multitouch_sensitivity_y;
429     m_sensitivity_y = std::min(std::max(m_sensitivity_y, 0.0f), 1.0f);
430 } // updateConfigParams
431 
432 // ----------------------------------------------------------------------------
433 /** Helper function that returns a steering factor for steering button.
434  *  \param value The axis value from 0 to 1.
435  *  \param sensitivity Value from 0 to 1.0.
436  */
getSteeringFactor(float value,float sensitivity)437 float MultitouchDevice::getSteeringFactor(float value, float sensitivity)
438 {
439     if (m_deadzone >= 1.0f)
440         return 0.0f;
441 
442     if (sensitivity >= 1.0f)
443         return 1.0f;
444 
445     float factor = (value - m_deadzone) / (1.0f - m_deadzone);
446     factor *= 1.0f / (1.0f - sensitivity);
447 
448     return std::min(factor, 1.0f);
449 }
450 
451 // ----------------------------------------------------------------------------
452 
updateAxisX(float value)453 void MultitouchDevice::updateAxisX(float value)
454 {
455     if (m_controller == NULL)
456         return;
457 
458     if (value < -m_deadzone)
459     {
460         float factor = getSteeringFactor(std::abs(value), m_sensitivity_x);
461         m_controller->action(PA_STEER_RIGHT, 0);
462         m_controller->action(PA_STEER_LEFT, int(factor * Input::MAX_VALUE));
463     }
464     else if (value > m_deadzone)
465     {
466         float factor = getSteeringFactor(std::abs(value), m_sensitivity_x);
467         m_controller->action(PA_STEER_LEFT, 0);
468         m_controller->action(PA_STEER_RIGHT, int(factor * Input::MAX_VALUE));
469     }
470     else
471     {
472         m_controller->action(PA_STEER_LEFT, 0);
473         m_controller->action(PA_STEER_RIGHT, 0);
474     }
475 }
476 
477 // ----------------------------------------------------------------------------
478 
updateAxisY(float value)479 void MultitouchDevice::updateAxisY(float value)
480 {
481     if (m_controller == NULL)
482         return;
483 
484     if (value < -m_deadzone)
485     {
486         float factor = getSteeringFactor(std::abs(value), m_sensitivity_y);
487         m_controller->action(PA_ACCEL, int(factor * Input::MAX_VALUE));
488     }
489     else if (value > m_deadzone)
490     {
491         float factor = getSteeringFactor(std::abs(value), m_sensitivity_y);
492         m_controller->action(PA_BRAKE, int(factor * Input::MAX_VALUE));
493     }
494     else
495     {
496         m_controller->action(PA_BRAKE, 0);
497         m_controller->action(PA_ACCEL, 0);
498     }
499 
500 }
501 
502 // ----------------------------------------------------------------------------
503 
504 /** Returns device orientation Z angle, in radians, where 0 is landscape
505  *  orientation parallel to the floor.
506  */
getOrientation()507 float MultitouchDevice::getOrientation()
508 {
509     return m_orientation;
510 }
511 
512 // ----------------------------------------------------------------------------
513 
514 /** Update device orientation from the accelerometer measurements.
515  *  Accelerometer is shaky, so it adjusts the orientation angle slowly.
516  *  \param x Accelerometer X axis
517  *  \param y Accelerometer Y axis
518  */
updateOrientationFromAccelerometer(float x,float y)519 void MultitouchDevice::updateOrientationFromAccelerometer(float x, float y)
520 {
521     const float ACCEL_DISCARD_THRESHOLD = 4.0f;
522     const float ACCEL_MULTIPLIER = 0.05f; // Slowly adjust the angle over time,
523                                           // this prevents shaking
524     const float ACCEL_CHANGE_THRESHOLD = 0.01f; // ~0.5 degrees
525 
526     // The device is flat on the table, cannot reliably determine the
527     // orientation
528     if (fabsf(x) + fabsf(y) < ACCEL_DISCARD_THRESHOLD)
529         return;
530 
531     float angle = atan2f(y, x);
532     if (angle > (M_PI / 2.0))
533     {
534         angle = (M_PI / 2.0);
535     }
536     if (angle < -(M_PI / 2.0))
537     {
538         angle = -(M_PI / 2.0);
539     }
540 
541     float delta = angle - m_orientation;
542     delta *= ACCEL_MULTIPLIER;
543     if (delta > ACCEL_CHANGE_THRESHOLD)
544     {
545         delta = ACCEL_CHANGE_THRESHOLD;
546     }
547     if (delta < -ACCEL_CHANGE_THRESHOLD)
548     {
549         delta = -ACCEL_CHANGE_THRESHOLD;
550     }
551 
552     m_orientation += delta;
553 
554     //Log::warn("Accel", "X %03.4f Y %03.4f angle %03.4f delta %03.4f "
555     //          "orientation %03.4f", x, y, angle, delta, m_orientation);
556 }
557 
558 // ----------------------------------------------------------------------------
559 
560 /** Update device orientation from the gyroscope measurements.
561  *  Gyroscope is not shaky and very sensitive, but drifts over time.
562  *  \param x Gyroscope Z axis
563  */
updateOrientationFromGyroscope(float z)564 void MultitouchDevice::updateOrientationFromGyroscope(float z)
565 {
566     const float GYRO_SPEED_THRESHOLD = 0.005f;
567 
568     uint64_t now = StkTime::getMonoTimeMs();
569     uint64_t delta = now - m_gyro_time;
570     m_gyro_time = now;
571     float timedelta = (float)delta / 1000.f;
572     if (timedelta > 0.5f)
573     {
574         timedelta = 0.1f;
575     }
576 
577     float angular_speed = -z;
578 
579     if (fabsf(angular_speed) < GYRO_SPEED_THRESHOLD)
580     {
581         angular_speed = 0.0f;
582     }
583 
584     m_orientation += angular_speed * timedelta;
585     if (m_orientation > (M_PI / 2.0))
586     {
587         m_orientation = (M_PI / 2.0);
588     }
589     if (m_orientation < -(M_PI / 2.0))
590     {
591         m_orientation = -(M_PI / 2.0);
592     }
593 
594     //Log::warn("Gyro", "Z %03.4f angular_speed %03.4f delta %03.4f "
595     //          "orientation %03.4f", z, angular_speed,
596     //          angular_speed * timedelta, m_orientation);
597 }
598 
599 // ----------------------------------------------------------------------------
600 
601 /** Sends proper action for player controller depending on the button type
602  *  and state.
603  *  \param button The button that should be handled.
604  */
handleControls(MultitouchButton * button)605 void MultitouchDevice::handleControls(MultitouchButton* button)
606 {
607     if (!isGameRunning())
608         return;
609 
610     if (button->type == MultitouchButtonType::BUTTON_ESCAPE)
611     {
612         StateManager::get()->escapePressed();
613     }
614 
615     if (m_controller != NULL && !RaceManager::get()->isWatchingReplay())
616     {
617         if (button->type == MultitouchButtonType::BUTTON_STEERING)
618         {
619             updateAxisX(button->axis_x);
620             updateAxisY(button->axis_y);
621         }
622         else if (button->type == MultitouchButtonType::BUTTON_UP_DOWN)
623         {
624             updateAxisY(button->axis_y);
625         }
626         else if (button->action != PA_BEFORE_FIRST)
627         {
628             int value = button->pressed ? Input::MAX_VALUE : 0;
629             m_controller->action(button->action, value);
630         }
631     }
632 
633     if (button->callback != NULL)
634     {
635         button->callback(button->id, button->pressed);
636     }
637 }
638 
639 // ----------------------------------------------------------------------------
640 
isGameRunning()641 bool MultitouchDevice::isGameRunning()
642 {
643     return StateManager::get()->getGameState() == GUIEngine::GAME &&
644            !GUIEngine::ModalDialog::isADialogActive() &&
645            !GUIEngine::ScreenKeyboard::isActive();
646 }
647 
648 // ----------------------------------------------------------------------------
649 
updateController()650 void MultitouchDevice::updateController()
651 {
652     if (m_player == NULL)
653     {
654         m_controller = NULL;
655         return;
656     }
657 
658     // Handle multitouch events only when race is running. It avoids to process
659     // it when pause dialog is active during the race. And there is no reason
660     // to use it for GUI navigation.
661     if (!isGameRunning())
662     {
663         m_controller = NULL;
664         return;
665     }
666 
667     AbstractKart* pk = m_player->getKart();
668 
669     if (pk == NULL)
670     {
671         m_controller = NULL;
672         return;
673     }
674 
675     m_controller = pk->getController();
676 }
677 
678 // ----------------------------------------------------------------------------
679