diff options
| author | Marc Palau <sucdepressec@gmail.com> | 2015-04-22 17:34:42 +0200 |
|---|---|---|
| committer | Marc Palau <sucdepressec@gmail.com> | 2015-04-22 17:34:42 +0200 |
| commit | ceb73257272e34a739b6225e1f30e4e377fdab77 (patch) | |
| tree | 08521b1830316d124739f2fa1553f7b9949295f1 /src/gestures.c | |
| parent | 7d2318c1677fd6e81b8cc63b040289148ca8adfc (diff) | |
| download | raylib-ceb73257272e34a739b6225e1f30e4e377fdab77.tar.gz raylib-ceb73257272e34a739b6225e1f30e4e377fdab77.zip | |
Added Gestures System for Android and Web
Diffstat (limited to 'src/gestures.c')
| -rw-r--r-- | src/gestures.c | 636 |
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 + + + + + + |
