aboutsummaryrefslogtreecommitdiff
path: root/src/gestures.c
diff options
context:
space:
mode:
authorMarc Palau <sucdepressec@gmail.com>2015-04-22 17:34:42 +0200
committerMarc Palau <sucdepressec@gmail.com>2015-04-22 17:34:42 +0200
commitceb73257272e34a739b6225e1f30e4e377fdab77 (patch)
tree08521b1830316d124739f2fa1553f7b9949295f1 /src/gestures.c
parent7d2318c1677fd6e81b8cc63b040289148ca8adfc (diff)
downloadraylib-ceb73257272e34a739b6225e1f30e4e377fdab77.tar.gz
raylib-ceb73257272e34a739b6225e1f30e4e377fdab77.zip
Added Gestures System for Android and Web
Diffstat (limited to 'src/gestures.c')
-rw-r--r--src/gestures.c636
1 files changed, 636 insertions, 0 deletions
diff --git a/src/gestures.c b/src/gestures.c
new file mode 100644
index 00000000..594c3cd5
--- /dev/null
+++ b/src/gestures.c
@@ -0,0 +1,636 @@
+/**********************************************************************************************
+*
+* raylib.gestures
+*
+* Gestures Detection and Usage Functions Definitions
+*
+* Copyright (c) 2015 Marc Palau and Ramon Santamaria
+*
+* This software is provided "as-is", without any express or implied warranty. In no event
+* will the authors be held liable for any damages arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose, including commercial
+* applications, and to alter it and redistribute it freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not claim that you
+* wrote the original software. If you use this software in a product, an acknowledgment
+* in the product documentation would be appreciated but is not required.
+*
+* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
+* as being the original software.
+*
+* 3. This notice may not be removed or altered from any source distribution.
+*
+**********************************************************************************************/
+
+#include "raylib.h"
+#include "raymath.h"
+#include "utils.h"
+
+#include <stdlib.h> // malloc(), free()
+#include <stdio.h> // printf(), fprintf()
+#include <math.h> // Used for ...
+#include <time.h>
+
+#if defined(PLATFORM_ANDROID)
+ #include <jni.h> // Java native interface
+ #include <android/sensor.h> // Android sensors functions
+ #include <android/window.h> // Defines AWINDOW_FLAG_FULLSCREEN and others
+#endif
+
+#if defined(PLATFORM_WEB)
+ #include <emscripten/emscripten.h>
+ #include <emscripten/emscripten/html5.h>
+#endif
+
+//----------------------------------------------------------------------------------
+// Defines and Macros
+//----------------------------------------------------------------------------------
+#define FORCE_TO_SWIPE 20
+#define TAP_TIMEOUT 300
+
+#define MAX_TOUCH_POINTS 4
+
+typedef enum {
+ TYPE_MOTIONLESS,
+ TYPE_DRAG,
+ TYPE_DUAL_INPUT
+} GestureType;
+
+typedef enum {
+ UP,
+ DOWN,
+ MOVE
+} ActionType;
+
+typedef struct {
+ ActionType action;
+ int pointCount;
+ int pointerId[MAX_TOUCH_POINTS];
+ Vector2 position[MAX_TOUCH_POINTS];
+} GestureEvent;
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+
+// typedef
+static GestureType gestureType = TYPE_MOTIONLESS;
+
+// Gestures detection variables
+static int32_t touchId;
+
+// Event
+static int64_t eventTime = 0;
+
+// Tap
+// Our initial press position on tap
+static Vector2 initialTapPosition = { 0, 0 };
+
+// Double tap
+// If we are double tapping or not
+static bool doubleTapping = false;
+// If we recently made a tap
+static bool untap = false;
+
+// Drag
+// Our initial press position on drag
+static Vector2 initialDragPosition = { 0, 0 };
+// Position that will compare itself with the mouse one
+static Vector2 endDragPosition = { 0, 0 };
+// Position of the last event detection
+static Vector2 lastDragPosition = { 0, 0 };
+// The total drag vector
+static Vector2 dragVector = { 0, 0 };
+// The distance traveled dragging
+static float magnitude = 0;
+// The angle direction of the drag
+static float angle = 0;
+// A magnitude to calculate how fast we did the drag ( pixels per frame )
+static float intensity = 0;
+// Time that have passed while dragging
+static int draggingTimeCounter = 0;
+
+// Pinch
+// First initial pinch position
+static Vector2 firstInitialPinchPosition = { 0, 0 };
+// Second initial pinch position
+static Vector2 secondInitialPinchPosition = { 0, 0 };
+// First end pinch position
+static Vector2 firstEndPinchPosition = { 0, 0 };
+// Second end pinch position
+static Vector2 secondEndPinchPosition = { 0, 0 };
+// Delta Displacement
+static float pinchDelta = 0;
+
+// Detected gesture
+static int currentGesture = GESTURE_NONE;
+
+static float touchX, touchY;
+
+//----------------------------------------------------------------------------------
+// Module specific Functions Declaration
+//----------------------------------------------------------------------------------
+extern void ProcessMotionEvent(GestureEvent event);
+extern void ResetGestures(void);
+extern Vector2 GetRawPosition(void);
+
+static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude);
+static float OnPinch();
+static void SetDualInput(GestureEvent event);
+static float Distance(Vector2 v1, Vector2 v2);
+static float DotProduct(Vector2 v1, Vector2 v2);
+static int GetCurrentTime();
+
+#if defined(PLATFORM_WEB)
+static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
+#endif
+
+#if defined(PLATFORM_ANDROID)
+static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event);
+#endif
+
+//----------------------------------------------------------------------------------
+// Module Functions Definition
+//----------------------------------------------------------------------------------
+
+// Returns tap position XY
+extern Vector2 GetRawPosition(void)
+{
+ Vector2 position = { touchX, touchY };
+
+ return position;
+}
+
+// Check if a gesture have been detected
+bool IsGestureDetected(void)
+{
+ if (currentGesture == GESTURE_DRAG) TraceLog(INFO, "DRAG");
+ else if (currentGesture == GESTURE_TAP) TraceLog(INFO, "TAP");
+ else if (currentGesture == GESTURE_DOUBLETAP) TraceLog(INFO, "DOUBLE");
+ else if (currentGesture == GESTURE_HOLD) TraceLog(INFO, "HOLD");
+ else if (currentGesture == GESTURE_SWIPE_RIGHT) TraceLog(INFO, "RIGHT");
+ else if (currentGesture == GESTURE_SWIPE_UP) TraceLog(INFO, "UP");
+ else if (currentGesture == GESTURE_SWIPE_LEFT) TraceLog(INFO, "LEFT");
+ else if (currentGesture == GESTURE_SWIPE_DOWN) TraceLog(INFO, "DOWN");
+ else if (currentGesture == GESTURE_PINCH_IN) TraceLog(INFO, "PINCH IN");
+ else if (currentGesture == GESTURE_PINCH_OUT) TraceLog(INFO, "PINCH OUT");
+
+ if (currentGesture != GESTURE_NONE) return false;
+ else return true;
+}
+
+// Check gesture type
+int GetGestureType(void)
+{
+ return currentGesture;
+}
+
+// Get drag intensity (pixels per frame)
+float GetDragIntensity(void)
+{
+ return intensity;
+}
+
+// Get drag angle
+// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise
+float GetDragAngle(void)
+{
+ return angle;
+}
+
+// Get drag vector (between initial and final position)
+Vector2 GetDragVector(void)
+{
+ return dragVector;
+}
+
+// Hold time measured in frames
+int GetHoldDuration(void)
+{
+ return 0;
+}
+
+// Get magnitude between two pinch points
+float GetPinchDelta(void)
+{
+ return pinchDelta;
+}
+
+// Get angle beween two pinch points
+// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise
+float GetPinchAngle(void)
+{
+ return 0;
+}
+
+extern void ResetGestures(void)
+{
+ if (currentGesture != GESTURE_HOLD) currentGesture = GESTURE_NONE;
+}
+
+#if defined(PLATFORM_WEB)
+extern void InitWebGestures(void)
+{
+ /*
+ emscripten_set_touchstart_callback("#canvas", data, 0, Emscripten_HandleTouch);
+ emscripten_set_touchend_callback("#canvas", data, 0, Emscripten_HandleTouch);
+ emscripten_set_touchmove_callback("#canvas", data, 0, Emscripten_HandleTouch);
+ emscripten_set_touchcancel_callback("#canvas", data, 0, Emscripten_HandleTouch);
+ */
+
+ //emscripten_set_touchstart_callback(0, NULL, 1, Emscripten_HandleTouch);
+
+ emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenInputCallback);
+ emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenInputCallback);
+ emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenInputCallback);
+ emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenInputCallback);
+}
+#endif
+
+#if defined(PLATFORM_ANDROID)
+extern void InitAndroidGestures(struct android_app *app)
+{
+ app->onInputEvent = AndroidInputCallback;
+
+ // TODO: Receive frameBuffer data: displayWidth/displayHeight, renderWidth/renderHeight, screenWidth/screenHeight
+}
+#endif
+
+//----------------------------------------------------------------------------------
+// Module specific Functions Definition
+//----------------------------------------------------------------------------------
+extern void ProcessMotionEvent(GestureEvent event)
+{
+ // Resets
+ dragVector = (Vector2){ 0, 0 };
+ pinchDelta = 0;
+
+ switch (gestureType)
+ {
+ case TYPE_MOTIONLESS: // Detect TAP, DOUBLE_TAP and HOLD events
+ {
+ if (event.action == DOWN)
+ {
+ if (event.pointCount > 1) SetDualInput(event);
+ else
+ {
+ // Set the press position
+ initialTapPosition = event.position[0];
+
+ // If too much time have passed, we reset the double tap
+ if (GetCurrentTime() - eventTime > TAP_TIMEOUT) untap = false;
+
+ // If we are in time, we detect the double tap
+ if (untap) doubleTapping = true;
+
+ // Update our event time
+ eventTime = GetCurrentTime();
+
+ // Set hold
+ currentGesture = GESTURE_HOLD;
+ }
+ }
+ else if (event.action == UP)
+ {
+ // Detect that we are tapping instead of holding
+ if (GetCurrentTime() - eventTime < TAP_TIMEOUT)
+ {
+ if (doubleTapping)
+ {
+ // If we tapped before we define it as double tap
+ currentGesture = GESTURE_DOUBLETAP;
+ untap = false;
+ }
+ else
+ {
+ // Simple tap
+ currentGesture = GESTURE_TAP;
+ untap = true;
+ }
+ }
+ else currentGesture = GESTURE_NONE;
+
+ // Tap finished
+ doubleTapping = false;
+ // Update our event time
+ eventTime = GetCurrentTime();
+ }
+ // Begin dragging
+ else if (event.action == MOVE)
+ {
+ if (event.pointCount > 1) SetDualInput(event);
+ else
+ {
+ // Set the drag starting position
+ initialDragPosition = initialTapPosition;
+ endDragPosition = initialDragPosition;
+
+ // Initialize drag
+ draggingTimeCounter = 0;
+ gestureType = TYPE_DRAG;
+ currentGesture = GESTURE_NONE;
+ }
+ }
+ } break;
+ case TYPE_DRAG: // Detect DRAG and SWIPE events
+ {
+ // end of the drag
+ if (event.action == UP)
+ {
+ // Return Swipe if we have enough sensitivity
+ if (intensity > FORCE_TO_SWIPE)
+ {
+ if (angle < 30 || angle > 330) currentGesture = GESTURE_SWIPE_RIGHT; // Right
+ else if (angle > 60 && angle < 120) currentGesture = GESTURE_SWIPE_UP; // Up
+ else if (angle > 150 && angle < 210) currentGesture = GESTURE_SWIPE_LEFT; // Left
+ else if (angle > 240 && angle < 300) currentGesture = GESTURE_SWIPE_DOWN; // Down
+ }
+
+ magnitude = 0;
+ angle = 0;
+ intensity = 0;
+
+ gestureType = TYPE_MOTIONLESS;
+ }
+ // Update while we are dragging
+ else if (event.action == MOVE)
+ {
+ if (event.pointCount > 1) SetDualInput(event);
+ else
+ {
+ lastDragPosition = endDragPosition;
+
+ endDragPosition = GetRawPosition();
+
+ //endDragPosition.x = AMotionEvent_getX(event, 0);
+ //endDragPosition.y = AMotionEvent_getY(event, 0);
+
+ // Calculate attributes
+ dragVector = (Vector2){ endDragPosition.x - lastDragPosition.x, endDragPosition.y - lastDragPosition.y };
+ magnitude = sqrt(pow(endDragPosition.x - initialDragPosition.x, 2) + pow(endDragPosition.y - initialDragPosition.y, 2));
+ angle = CalculateAngle(initialDragPosition, endDragPosition, magnitude);
+ intensity = magnitude / (float)draggingTimeCounter;
+
+ currentGesture = GESTURE_DRAG;
+ draggingTimeCounter++;
+ }
+ }
+ } break;
+ case TYPE_DUAL_INPUT:
+ {
+ if (event.action == UP)
+ {
+ if (event.pointCount == 1)
+ {
+ // Set the drag starting position
+ initialTapPosition = event.position[0];
+ }
+ gestureType = TYPE_MOTIONLESS;
+ }
+ else if (event.action == MOVE)
+ {
+ // Adapt the ending position of the inputs
+ firstEndPinchPosition = event.position[0];
+ secondEndPinchPosition = event.position[1];
+
+ // If there is no more than two inputs
+ if (event.pointCount == 2)
+ {
+ // Detect pinch delta
+ pinchDelta = OnPinch();
+
+ // Pinch gesture resolution
+ if (pinchDelta != 0)
+ {
+ if (pinchDelta > 0) currentGesture = GESTURE_PINCH_IN;
+ else currentGesture = GESTURE_PINCH_OUT;
+ }
+ }
+ else
+ {
+ // Set the drag starting position
+ initialTapPosition = event.position[0];
+
+ gestureType = TYPE_MOTIONLESS;
+ }
+
+ // Readapt the initial position of the inputs
+ firstInitialPinchPosition = firstEndPinchPosition;
+ secondInitialPinchPosition = secondEndPinchPosition;
+ }
+ } break;
+ }
+ //--------------------------------------------------------------------
+}
+
+
+static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude)
+{
+ float angle;
+
+ // Calculate arcsinus of the movement ( Our sinus is (actualPosition.y - initialPosition.y) / magnitude)
+ angle = asin((actualPosition.y - initialPosition.y) / magnitude);
+ angle *= RAD2DEG;
+
+ // Calculate angle depending on the sector
+ if (actualPosition.x - initialPosition.x >= 0)
+ {
+ // Sector 4
+ if (actualPosition.y - initialPosition.y >= 0)
+ {
+ angle *= -1;
+ angle += 360;
+ }
+ // Sector 1
+ else
+ {
+ angle *= -1;
+ }
+ }
+ else
+ {
+ // Sector 3
+ if (actualPosition.y - initialPosition.y >= 0)
+ {
+ angle += 180;
+ }
+ // Sector 2
+ else
+ {
+ angle *= -1;
+ angle = 180 - angle;
+ }
+ }
+
+ return angle;
+}
+
+static float OnPinch()
+{
+ // Calculate distances
+ float initialDistance = Distance(firstInitialPinchPosition, secondInitialPinchPosition);
+ float endDistance = Distance(firstEndPinchPosition, secondEndPinchPosition);
+
+ // Calculate Vectors
+ Vector2 firstTouchVector = { firstEndPinchPosition.x - firstInitialPinchPosition.x, firstEndPinchPosition.y - firstInitialPinchPosition.y };
+ Vector2 secondTouchVector = { secondEndPinchPosition.x - secondInitialPinchPosition.x, secondEndPinchPosition.y - secondInitialPinchPosition.y };
+
+ // Detect the pinch gesture
+ // Calculate Distances
+ if (DotProduct(firstTouchVector, secondTouchVector) < -0.5) return initialDistance - endDistance;
+ else return 0;
+}
+
+static void SetDualInput(GestureEvent event)
+{
+ initialDragPosition = (Vector2){ 0, 0 };
+ endDragPosition = (Vector2){ 0, 0 };
+ lastDragPosition = (Vector2){ 0, 0 };
+
+ // Initialize positions
+ firstInitialPinchPosition = event.position[0];
+ secondInitialPinchPosition = event.position[1];
+
+ firstEndPinchPosition = firstInitialPinchPosition;
+ secondEndPinchPosition = secondInitialPinchPosition;
+
+ // Resets
+ magnitude = 0;
+ angle = 0;
+ intensity = 0;
+
+ gestureType = TYPE_DUAL_INPUT;
+}
+
+static float Distance(Vector2 v1, Vector2 v2)
+{
+ float result;
+
+ float dx = v2.x - v1.x;
+ float dy = v2.y - v1.y;
+
+ result = sqrt(dx*dx + dy*dy);
+
+ return result;
+}
+
+static float DotProduct(Vector2 v1, Vector2 v2)
+{
+ float result;
+
+ float v1Module = sqrt(v1.x*v1.x + v1.y*v1.y);
+ float v2Module = sqrt(v2.x*v2.x + v2.y*v2.y);
+
+ Vector2 v1Normalized = { v1.x / v1Module, v1.y / v1Module };
+ Vector2 v2Normalized = { v2.x / v2Module, v2.y / v2Module };
+
+ result = v1Normalized.x*v2Normalized.x + v1Normalized.y*v2Normalized.y;
+
+ return result;
+}
+
+static int GetCurrentTime()
+{
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time provided in nanoseconds
+
+ return nowTime / 1000000; // Return time in miliseconds
+}
+
+#if defined(PLATFORM_ANDROID)
+// Android: Process activity input events
+static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event)
+{
+ int type = AInputEvent_getType(event);
+ //int32_t key = 0;
+
+ if (type == AINPUT_EVENT_TYPE_MOTION)
+ {
+ touchX = AMotionEvent_getX(event, 0);
+ touchY = AMotionEvent_getY(event, 0);
+ }
+ else if (type == AINPUT_EVENT_TYPE_KEY)
+ {
+ //key = AKeyEvent_getKeyCode(event);
+ //int32_t AKeyEvent_getMetaState(event);
+ }
+
+ int32_t action = AMotionEvent_getAction(event);
+ unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
+
+
+ GestureEvent gestureEvent;
+
+ // Action
+ if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.action = DOWN;
+ else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.action = UP;
+ else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.action = MOVE;
+
+ // Points
+ gestureEvent.pointCount = AMotionEvent_getPointerCount(event);
+
+ // Position
+ gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) };
+ gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) };
+
+ ProcessMotionEvent(gestureEvent);
+
+ return 0;
+}
+#endif
+
+#if defined(PLATFORM_WEB)
+static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
+{
+ /*
+ for (int i = 0; i < touchEvent->numTouches; i++)
+ {
+ long x, y, id;
+
+ if (!touchEvent->touches[i].isChanged) continue;
+
+ id = touchEvent->touches[i].identifier;
+ x = touchEvent->touches[i].canvasX;
+ y = touchEvent->touches[i].canvasY;
+ }
+
+ printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches,
+ event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "");
+
+ for(int i = 0; i < event->numTouches; ++i)
+ {
+ const EmscriptenTouchPoint *t = &event->touches[i];
+
+ printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n",
+ t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY);
+ }
+ */
+
+ GestureEvent gestureEvent;
+
+ // Action
+ if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.action = DOWN;
+ else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.action = UP;
+ else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.action = MOVE;
+
+ // Points
+ gestureEvent.pointCount = event->numTouches;
+
+ // Position
+ gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY };
+ gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY };
+
+
+ ProcessMotionEvent(gestureEvent);
+
+ return 1;
+}
+#endif
+
+
+
+
+
+