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