SDL_gesture: Initial add.

From 02a32167df0140a090bd78abcb670587c1cad80a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 16 Dec 2022 21:46:54 -0500
Subject: [PATCH] Initial add.

---
 LICENSE.txt   |  18 ++
 README.md     |  44 +++
 SDL_gesture.h | 854 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 916 insertions(+)
 create mode 100644 LICENSE.txt
 create mode 100644 README.md
 create mode 100644 SDL_gesture.h

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..728a3d7
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+  
+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.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0500ece
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+## SDL_gesture
+
+SDL3 is removes the Gesture API that was available in SDL2, so as a migration
+path, we are providing an equivalent as a single-header library that you
+can drop into your SDL3-based project.
+
+We do not make formal releases of this code; just grab the latest and drop
+it into your project!
+
+To use:
+
+- Copy SDL_gesture.h into your project.
+- Wherever you need access to this functionality, `#include` the header,
+  _after_ including SDL.h.
+- In **ONLY ONE PLACE** in your project, make sure you've `#defined`
+  SDL_GESTURE_IMPLEMENTATION before including the header:
+
+
+  ```c
+  #define SDL_GESTURE_IMPLEMENTATION 1
+  #include "SDL_gesture.h"
+  ```
+
+  This will make the header include not just function declarations and such
+  but also its implementation code.
+- To use the API, call this somewhere near startup, after SDL_Init:
+
+  ```c
+  Gesture_Init();
+  ```
+
+  and then, before you call SDL_Quit at the end of your program:
+
+  ```c
+  Gesture_Quit();
+  ```
+
+  Now you will get Gesture events from the SDL event queue.
+- SDL_RecordGesture, SDL_SaveAllDollarTemplates, SDL_SaveDollarTemplate, and
+  SDL_LoadDollarTemplates work as before, they are just prefixed with
+  "Gesture_" instead of "SDL_". Same with event types.
+- It is safe to add this to SDL2-based projects, as it will just use SDL2's
+  existing API under the hood.
+
diff --git a/SDL_gesture.h b/SDL_gesture.h
new file mode 100644
index 0000000..0c6b5b8
--- /dev/null
+++ b/SDL_gesture.h
@@ -0,0 +1,854 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  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.
+*/
+
+/* Touch gestures were removed from SDL3, so this is the SDL2 implementation copied in here, and tweaked a little. */
+
+#ifndef INCL_SDL_GESTURE_H
+#define INCL_SDL_GESTURE_H
+
+#if !defined(SDL_VERSION_MAJOR)
+#error Please include SDL.h before including this header.
+#elif SDL_VERSION_MAJOR < 2
+#error This header requires SDL2 or later.
+#elif SDL_VERSION_MAJOR == 2
+/* building against SDL2? Just use the built-in SDL2 implementation. */
+#define Gesture_Init() (0)
+#define Gesture_ID SDL_GestureID
+#define Gesture_RecordGesture SDL_RecordGesture
+#define Gesture_SaveAllDollarTemplates SDL_SaveAllDollarTemplates
+#define Gesture_SaveDollarTemplate SDL_SaveDollarTemplate
+#define GESTURE_DOLLARGESTURE SDL_DOLLARGESTURE
+#define GESTURE_DOLLARRECORD SDL_DOLLARRECORD
+#define GESTURE_MULTIGESTURE SDL_MULTIGESTURE
+#define Gesture_MultiGestureEvent SDL_MultiGestureEvent
+#define Gesture_DollarGestureEvent SDL_MultiGestureEvent
+#else
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef Sint64 Gesture_ID;
+
+/* events... */
+
+/* generally you shouldn't hardcode event type numbers--and doubly so in
+   the reserved range!--but these match SDL2 and SDL3 promises to preserve
+   these values to help sdl2-compat. */
+#define GESTURE_DOLLARGESTURE 0x800
+#define GESTURE_DOLLARRECORD 0x801
+#define GESTURE_MULTIGESTURE 0x802
+
+typedef struct Gesture_MultiGestureEvent
+{
+    Uint32 type;
+    Uint32 timestamp;
+    SDL_TouchID touchId;
+    float dTheta;
+    float dDist;
+    float x;
+    float y;
+    Uint16 numFingers;
+    Uint16 padding;
+} Gesture_MultiGestureEvent;
+
+typedef struct Gesture_DollarGestureEvent
+{
+    Uint32 type;
+    Uint32 timestamp;
+    SDL_TouchID touchId;
+    SDL_GestureID gestureId;
+    Uint32 numFingers;
+    float error;
+    float x;
+    float y;
+} Gesture_DollarGestureEvent;
+
+
+/* Function prototypes */
+
+/**
+ * Call this once, AFTER SDL_Init, to set up the Gesture API.
+ *
+ * \returns 0 on success, -1 on error. Call SDL_GetError() for specifics.
+ */
+extern int SDLCALL Gesture_Init(void);
+
+/**
+ * Call this once, BEFORE SDL_Quit, to clean up the Gesture API.
+ */
+extern void SDLCALL Gesture_Quit(void);
+
+/**
+ * Begin recording a gesture on a specified touch device or all touch devices.
+ *
+ * If the parameter `touchId` is -1 (i.e., all devices), this function will
+ * always return 1, regardless of whether there actually are any devices.
+ *
+ * \param touchId the touch device id, or -1 for all touch devices
+ * \returns 1 on success or 0 if the specified device could not be found.
+ */
+extern int SDLCALL Gesture_RecordGesture(SDL_TouchID touchId);
+
+/**
+ * Save all currently loaded Dollar Gesture templates.
+ *
+ * \param dst a SDL_RWops to save to
+ * \returns the number of saved templates on success or 0 on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 2.0.0.
+ *
+ * \sa Gesture_LoadDollarTemplates
+ * \sa Gesture_SaveDollarTemplate
+ */
+extern int SDLCALL Gesture_SaveAllDollarTemplates(SDL_RWops *dst);
+
+/**
+ * Save a currently loaded Dollar Gesture template.
+ *
+ * \param gestureId a gesture id
+ * \param dst a SDL_RWops to save to
+ * \returns 1 on success or 0 on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \since This function is available since SDL 2.0.0.
+ *
+ * \sa SDL_LoadDollarTemplates
+ * \sa SDL_SaveAllDollarTemplates
+ */
+extern int SDLCALL Gesture_SaveDollarTemplate(SDL_GestureID gestureId,SDL_RWops *dst);
+
+/**
+ * Load Dollar Gesture templates from a file.
+ *
+ * \param touchId a touch id
+ * \param src a SDL_RWops to load from
+ * \returns the number of loaded templates on success or a negative error code
+ *          (or 0) on failure; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 2.0.0.
+ *
+ * \sa SDL_SaveAllDollarTemplates
+ * \sa SDL_SaveDollarTemplate
+ */
+extern int SDLCALL Gesture_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !defined INCL_SDL_GESTURE_H */
+
+
+#if defined(SDL_GESTURE_IMPLEMENTATION)
+
+#define GESTURE_MAX_DOLLAR_PATH_SIZE 1024
+#define GESTURE_DOLLARNPOINTS 64
+#define GESTURE_DOLLARSIZE 256
+#define GESTURE_PHI        0.618033989
+
+typedef struct
+{
+    float length;
+    int numPoints;
+    SDL_FPoint p[GESTURE_MAX_DOLLAR_PATH_SIZE];
+} GestureDollarPath;
+
+typedef struct
+{
+    SDL_FPoint path[GESTURE_DOLLARNPOINTS];
+    unsigned long hash;
+} GestureDollarTemplate;
+
+typedef struct
+{
+    SDL_TouchID touchId;
+    SDL_FPoint centroid;
+    GestureDollarPath dollarPath;
+    Uint16 numDownFingers;
+    int numDollarTemplates;
+    GestureDollarTemplate *dollarTemplate;
+    SDL_bool recording;
+} GestureTouch;
+
+static GestureTouch *GestureTouches = NULL;
+static int GestureNumTouches = 0;
+static SDL_bool GestureRecordAll = SDL_FALSE;
+
+
+static void GestureProcessEvent(const SDL_Event *event);
+
+static int SDLCALL *GestureEventWatch(void *userdata, SDL_Event *event)
+{
+    GestureProcessEvent(event);
+    return 1;
+}
+
+int Gesture_Init(void)
+{
+    Gesture_Quit();
+    SDL_AddEventWatch(GestureEventWatch, NULL);
+    return 0;
+}
+
+
+static GestureTouch *GestureAddTouch(const SDL_TouchID touchId)
+{
+    GestureTouch *gestureTouch = (GestureTouch *)SDL_realloc(GestureTouches, (GestureNumTouches + 1) * sizeof(GestureTouch));
+    if (gestureTouch == NULL) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    GestureTouches = gestureTouch;
+    SDL_zero(GestureTouches[GestureNumTouches]);
+    GestureTouches[GestureNumTouches].touchId = touchId;
+    return &GestureTouches[GestureNumTouches++];
+}
+
+static int GestureDelTouch(const SDL_TouchID touchId)
+{
+    int i;
+    for (i = 0; i < GestureNumTouches; i++) {
+        if (GestureTouches[i].touchId == touchId) {
+            break;
+        }
+    }
+
+    if (i == GestureNumTouches) {
+        /* not found */
+        return -1;
+    }
+
+    SDL_free(GestureTouches[i].dollarTemplate);
+    SDL_zero(GestureTouches[i]);
+
+    GestureNumTouches--;
+    if (i != GestureNumTouches) {
+        SDL_copyp(&GestureTouches[i], &GestureTouches[GestureNumTouches]);
+    }
+    return 0;
+}
+
+static GestureTouch *GestureGetTouch(const SDL_TouchID touchId)
+{
+    int i;
+    for (i = 0; i < GestureNumTouches; i++) {
+        /* printf("%i ?= %i\n",GestureTouches[i].touchId,touchId); */
+        if (GestureTouches[i].touchId == touchId) {
+            return &GestureTouches[i];
+        }
+    }
+    return NULL;
+}
+
+int SDL_RecordGesture(SDL_TouchID touchId)
+{
+    const int numtouchdevs = SDL_GetNumTouchDevices();
+    int i;
+
+    /* make sure we know about all the devices SDL3 knows about, since we aren't connected as tightly as we were in SDL2. */
+    for (i = 0; i < numtouchdevs; i++) {
+        const SDL_TouchID thistouch = SDL_GetTouchDevice(i);
+        if (!GestureGetTouch(thistouch)) {
+            if (!GestureAddTouch(thistouch)) {
+                return 0;  /* uhoh, out of memory */
+            }
+        }
+    }
+
+    if (touchId < 0) {
+        GestureRecordAll = SDL_TRUE;  /* !!! FIXME: this is never set back to SDL_FALSE anywhere, that's probably a bug. */
+        for (i = 0; i < GestureNumTouches; i++) {
+            GestureTouches[i].recording = SDL_TRUE;
+        }
+    } else {
+        GestureTouch *touch = GestureGetTouch(touchId);
+        if (!touch) {
+            return 0;  /* bogus touchid */
+        }
+        touch->recording = SDL_TRUE;
+    }
+
+    return 1;
+}
+
+void Gesture_Quit(void)
+{
+    SDL_DelEventWatch(GestureEventWatch, NULL);
+    SDL_free(GestureTouches);
+    GestureTouches = NULL;
+    GestureNumTouches = 0;
+    GestureRecordAll = SDL_FALSE;
+}
+
+static unsigned long GestureHashDollar(SDL_FPoint *points)
+{
+    unsigned long hash = 5381;
+    int i;
+    for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) {
+        hash = ((hash << 5) + hash) + (unsigned long)points[i].x;
+        hash = ((hash << 5) + hash) + (unsigned long)points[i].y;
+    }
+    return hash;
+}
+
+static int GestureSaveTemplate(GestureDollarTemplate *templ, SDL_RWops *dst)
+{
+    const Sint64 bytes = sizeof(templ->path[0]) * GESTURE_DOLLARNPOINTS;
+
+    if (dst == NULL) {
+        return 0;
+    }
+
+    /* No Longer storing the Hash, rehash on load */
+    /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash)) != sizeof(templ->hash)) return 0; */
+
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+    if (SDL_RWwrite(dst, templ->path, bytes) != bytes) {
+        return 0;
+    }
+#else
+    {
+        GestureDollarTemplate copy = *templ;
+        SDL_FPoint *p = copy.path;
+        int i;
+        for (i = 0; i < GESTURE_DOLLARNPOINTS; i++, p++) {
+            p->x = SDL_SwapFloatLE(p->x);
+            p->y = SDL_SwapFloatLE(p->y);
+        }
+
+        if (SDL_RWwrite(dst, copy.path, bytes) != bytes) {
+            return 0;
+        }
+    }
+#endif
+
+    return 1;
+}
+
+DECLSPEC int SDLCALL
+SDL_SaveAllDollarTemplates(SDL_RWops *dst)
+{
+    int i, j, rtrn = 0;
+    for (i = 0; i < GestureNumTouches; i++) {
+        GestureTouch *touch = &GestureTouches[i];
+        for (j = 0; j < touch->numDollarTemplates; j++) {
+            rtrn += GestureSaveTemplate(&touch->dollarTemplate[j], dst);
+        }
+    }
+    return rtrn;
+}
+
+DECLSPEC int SDLCALL
+SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst)
+{
+    int i, j;
+    for (i = 0; i < GestureNumTouches; i++) {
+        GestureTouch *touch = &GestureTouches[i];
+        for (j = 0; j < touch->numDollarTemplates; j++) {
+            if (touch->dollarTemplate[j].hash == gestureId) {
+                return GestureSaveTemplate(&touch->dollarTemplate[j], dst);
+            }
+        }
+    }
+    return SDL_SetError("Unknown gestureId");
+}
+
+/* path is an already sampled set of points
+Returns the index of the gesture on success, or -1 */
+static int GestureAddDollar_one(GestureTouch *inTouch, SDL_FPoint *path)
+{
+    GestureDollarTemplate *dollarTemplate;
+    GestureDollarTemplate *templ;
+    int index;
+
+    index = inTouch->numDollarTemplates;
+    dollarTemplate = (GestureDollarTemplate *)SDL_realloc(inTouch->dollarTemplate, (index + 1) * sizeof(GestureDollarTemplate));
+    if (dollarTemplate == NULL) {
+        return SDL_OutOfMemory();
+    }
+    inTouch->dollarTemplate = dollarTemplate;
+
+    templ = &inTouch->dollarTemplate[index];
+    SDL_memcpy(templ->path, path, GESTURE_DOLLARNPOINTS * sizeof(SDL_FPoint));
+    templ->hash = GestureHashDollar(templ->path);
+    inTouch->numDollarTemplates++;
+
+    return index;
+}
+
+static int GestureAddDollar(GestureTouch *inTouch, SDL_FPoint *path)
+{
+    int index = -1;
+    int i = 0;
+    if (inTouch == NULL) {
+        if (GestureNumTouches == 0) {
+            return SDL_SetError("no gesture touch devices registered");
+        }
+        for (i = 0; i < GestureNumTouches; i++) {
+            inTouch = &GestureTouches[i];
+            index = GestureAddDollar_one(inTouch, path);
+            if (index < 0) {
+                return -1;
+            }
+        }
+        /* Use the index of the last one added. */
+        return index;
+    }
+    return GestureAddDollar_one(inTouch, path);
+}
+
+DECLSPEC int SDLCALL
+SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
+{
+    int i, loaded = 0;
+    GestureTouch *touch = NULL;
+    if (src == NULL) {
+        return 0;
+    }
+    if (touchId >= 0) {
+        for (i = 0; i < GestureNumTouches; i++) {
+            if (GestureTouches[i].touchId == touchId) {
+                touch = &GestureTouches[i];
+            }
+        }
+        if (touch == NULL) {
+            return SDL_SetError("given touch id not found");
+        }
+    }
+
+    while (1) {
+        GestureDollarTemplate templ;
+        const Sint64 bytes = sizeof(templ->path[0]) * GESTURE_DOLLARNPOINTS;
+
+        if (SDL_RWread(src, templ.path, bytes) < bytes) {
+            if (loaded == 0) {
+                return SDL_SetError("could not read any dollar gesture from rwops");
+            }
+            break;
+        }
+
+#if SDL_BYTEORDER != SDL_LIL_ENDIAN
+        for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) {
+            SDL_FPoint *p = &templ.path[i];
+            p->x = SDL_SwapFloatLE(p->x);
+            p->y = SDL_SwapFloatLE(p->y);
+        }
+#endif
+
+        if (touchId >= 0) {
+            /* printf("Adding loaded gesture to 1 touch\n"); */
+            if (GestureAddDollar(touch, templ.path) >= 0) {
+                loaded++;
+            }
+        } else {
+            /* printf("Adding to: %i touches\n",GestureNumTouches); */
+            for (i = 0; i < GestureNumTouches; i++) {
+                touch = &GestureTouches[i];
+                /* printf("Adding loaded gesture to + touches\n"); */
+                /* TODO: What if this fails? */
+                GestureAddDollar(touch, templ.path);
+            }
+            loaded++;
+        }
+    }
+
+    return loaded;
+}
+
+static float GestureDollarDifference(SDL_FPoint *points, SDL_FPoint *templ, float ang)
+{
+    /*  SDL_FPoint p[GESTURE_DOLLARNPOINTS]; */
+    float dist = 0;
+    SDL_FPoint p;
+    int i;
+    for (i = 0; i < GESTURE_DOLLARNPOINTS; i++) {
+        p.x = points[i].x * SDL_cosf(ang) - points[i].y * SDL_sinf(ang);
+        p.y = points[i].x * SDL_sinf(ang) + points[i].y * SDL_cosf(ang);
+        dist += SDL_sqrtf((p.x - templ[i].x) * (p.x - templ[i].x) + (p.y - templ[i].y) * (p.y - templ[i].y));
+    }
+    return dist / GESTURE_DOLLARNPOINTS;
+}
+
+static float GestureBestDollarDifference(SDL_FPoint *points, SDL_FPoint *templ)
+{
+    /*------------BEGIN DOLLAR BLACKBOX------------------
+      -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-
+      -"http://depts.washington.edu/aimgroup/proj/dollar/"
+    */
+    double ta = -SDL_PI_D / 4;
+    double tb = SDL_PI_D / 4;
+    double dt = SDL_PI_D / 90;
+    float x1 = (float)(GESTURE_PHI * ta + (1 - GESTURE_PHI) * tb);
+    float f1 = GestureDollarDifference(points, templ, x1);
+    float x2 = (float)((1 - GESTURE_PHI) * ta + GESTURE_PHI * tb);
+    float f2 = GestureDollarDifference(points, templ, x2);
+    while (SDL_fabs(ta - tb) > dt) {
+        if (f1 < f2) {
+            tb = x2;
+            x2 = x1;
+            f2 = f1;
+            x1 = (float)(GESTURE_PHI * ta + (1 - GESTURE_PHI) * tb);
+            f1 = GestureDollarDifference(points, templ, x1);
+        } else {
+            ta = x1;
+            x1 = x2;
+            f1 = f2;
+            x2 = (float)((1 - GESTURE_PHI) * ta + GESTURE_PHI * tb);
+            f2 = GestureDollarDifference(points, templ, x2);
+        }
+    }
+    /*
+      if (f1 <= f2)
+          printf("Min angle (x1): %f\n",x1);
+      else if (f1 >  f2)
+          printf("Min angle (x2): %f\n",x2);
+    */
+    return SDL_min(f1, f2);
+}
+
+/* `path` contains raw points, plus (possibly) the calculated length */
+static int GestureDollarNormalize(const GestureDollarPath *path, SDL_FPoint *points, SDL_bool is_recording)
+{
+    int i;
+    float interval;
+    float dist;
+    int numPoints = 0;
+    SDL_FPoint centroid;
+    float xmin, xmax, ymin, ymax;
+    float ang;
+    float w, h;
+    float length = path->length;
+
+    /* Calculate length if it hasn't already been done */
+    if (length <= 0) {
+        for (i = 1; i < path->numPoints; i++) {
+            const float dx = path->p[i].x - path->p[i - 1].x;
+            const float dy = path->p[i].y - path->p[i - 1].y;
+            length += SDL_sqrtf(dx * dx + dy * dy);
+        }
+    }
+
+    /* Resample */
+    interval = length / (GESTURE_DOLLARNPOINTS - 1);
+    dist = interval;
+
+    centroid.x = 0;
+    centroid.y = 0;
+
+    /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); */
+    for (i = 1; i < path->numPoints; i++) {
+        const float d = SDL_sqrtf((path->p[i - 1].x - path->p[i].x) * (path->p[i - 1].x - path->p[i].x) + (path->p[i - 1].y - path->p[i].y) * (path->p[i - 1].y - path->p[i].y));
+        /* printf("d = %f dist = %f/%f\n",d,dist,interval); */
+        while (dist + d > interval) {
+            points[numPoints].x = path->p[i - 1].x +
+                                  ((interval - dist) / d) * (path->p[i].x - path->p[i - 1].x);
+            points[numPoints].y = path->p[i - 1].y +
+                                  ((interval - dist) / d) * (path->p[i].y - path->p[i - 1].y);
+            centroid.x += points[numPoints].x;
+            centroid.y += points[numPoints].y;
+            numPoints++;
+
+            dist -= interval;
+        }
+        dist += d;
+    }
+    if (numPoints < GESTURE_DOLLARNPOINTS - 1) {
+        if (is_recording) {
+            SDL_SetError("ERROR: NumPoints = %i", numPoints);
+        }
+        return 0;
+    }
+    /* copy the last point */
+    points[GESTURE_DOLLARNPOINTS - 1] = path->p[path->numPoints - 1];
+    numPoints = GESTURE_DOLLARNPOINTS;
+
+    centroid.x /= numPoints;
+    centroid.y /= numPoints;
+
+    /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */
+    /* Rotate Points so point 0 is left of centroid and solve for the bounding box */
+    xmin = centroid.x;
+    xmax = centroid.x;
+    ymin = centroid.y;
+    ymax = centroid.y;
+
+    ang = SDL_atan2f(centroid.y - points[0].y, centroid.x - points[0].x);
+
+    for (i = 0; i < numPoints; i++) {
+        const float px = points[i].x;
+        const float py = points[i].y;
+        points[i].x = (px - centroid.x) * SDL_cosf(ang) - (py - centroid.y) * SDL_sinf(ang) + centroid.x;
+        points[i].y = (px - centroid.x) * SDL_sinf(ang) + (py - centroid.y) * SDL_cosf(ang) + centroid.y;
+
+        if (points[i].x < xmin) {
+            xmin = points[i].x;
+        }
+        if (points[i].x > xmax) {
+            xmax = points[i].x;
+        }
+        if (points[i].y < ymin) {
+            ymin = points[i].y;
+        }
+        if (points[i].y > ymax) {
+            ymax = points[i].y;
+        }
+    }
+
+    /* Scale points to GESTURE_DOLLARSIZE, and translate to the origin */
+    w = xmax - xmin;
+    h = ymax - ymin;
+
+    for (i = 0; i < numPoints; i++) {
+        points[i].x = (points[i].x - centroid.x) * GESTURE_DOLLARSIZE / w;
+        points[i].y = (points[i].y - centroid.y) * GESTURE_DOLLARSIZE / h;
+    }
+    return numPoints;
+}
+
+static float GestureDollarRecognize(const GestureDollarPath *path, int *bestTempl, GestureTouch *touch)
+{
+    SDL_FPoint points[GESTURE_DOLLARNPOINTS];
+    int i;
+    float bestDiff = 10000;
+
+    SDL_memset(points, 0, sizeof(points));
+
+    GestureDollarNormalize(path, points, SDL_FALSE);
+
+    /* PrintPath(points); */
+    *bestTempl = -1;
+    for (i = 0; i < touch->numDollarTemplates; i++) {
+        const float diff = GestureBestDollarDifference(points, touch->dollarTemplate[i].path);
+        if (diff < bestDiff) {
+            bestDiff = diff;
+            *bestTempl = i;
+        }
+    }
+    return bestDiff;
+}
+
+static void GestureSendMulti(GestureTouch *touch, float dTheta, float dDist)
+{
+    if (SDL_GetEventState(SDL_MULTIGESTURE) == SDL_ENABLE) {
+        SDL_Event event;
+        event.type = GESTURE_MULTIGESTURE;
+        event.common.timestamp = 0;
+        event.mgesture.touchId = touch->touchId;
+        event.mgesture.x = touch->centroid.x;
+        event.mgesture.y = touch->centroid.y;
+        event.mgesture.dTheta = dTheta;
+        event.mgesture.dDist = dDist;
+        event.mgesture.numFingers = touch->numDownFingers;
+        SDL_PushEvent(&event);
+    }
+}
+
+static void GestureSendDollar(GestureTouch *touch, SDL_GestureID gestureId, float error)
+{
+    if (SDL_GetEventState(SDL_DOLLARGESTURE) == SDL_ENABLE) {
+        SDL_Event event;
+        event.type = GESTURE_DOLLARGESTURE;
+        event.common.timestamp = 0;
+        event.dgesture.touchId = touch->touchId;
+        event.dgesture.x = touch->centroid.x;
+        event.dgesture.y = touch->centroid.y;
+        event.dgesture.gestureId = gestureId;
+        event.dgesture.error = error;
+        /* A finger came up to trigger this event. */
+        event.dgesture.numFingers = touch->numDownFingers + 1;
+        SDL_PushEvent(&event);
+    }
+}
+
+static void GestureSendDollarRecord(GestureTouch *touch, SDL_GestureID gestureId)
+{
+    if (SDL_GetEventState(SDL_DOLLARRECORD) == SDL_ENABLE) {
+        SDL_Event event;
+        event.type = GESTURE_DOLLARRECORD;
+        event.common.timestamp = 0;
+        event.dgesture.touchId = touch->touchId;
+        event.dgesture.gestureId = gestureId;
+        SDL_PushEvent(&event);
+    }
+}
+
+static void GestureProcessEvent(const SDL_Event *event)
+{
+    float x, y;
+    int index;
+    int i;
+    float pathDx, pathDy;
+    SDL_FPoint lastP;
+    SDL_FPoint lastCentroid;
+    float lDist;
+    float Dist;
+    float dtheta;
+    float dDist;
+
+    if (event->type == SDL_FINGERMOTION || event->type == SDL_FINGERDOWN || event->type == SDL_FINGERUP) {
+        GestureTouch *inTouch = GestureGetTouch(event->tfinger.touchId);
+
+        if (inTouch == NULL) {  /* we maybe didn't see this one before. */
+            inTouch = GestureAddTouch(event->tfinger.touchId);
+            if (!inTouch) {
+                return;  /* oh well. */
+            }
+        }
+
+        x = event->tfinger.x;
+        y = event->tfinger.y;
+
+        /* Finger Up */
+        if (event->type == SDL_FINGERUP) {
+            SDL_FPoint path[GESTURE_DOLLARNPOINTS];
+            inTouch->numDownFingers--;
+
+            if (inTouch->recording) {
+                inTouch->recording = SDL_FALSE;
+                GestureDollarNormalize(&inTouch->dollarPath, path, SDL_TRUE);
+                /* PrintPath(path); */
+                if (GestureRecordAll) {
+                    index = GestureAddDollar(NULL, path);
+                    for (i = 0; i < GestureNumTouches; i++) {
+                        GestureTouches[i].recording = SDL_FALSE;
+                    }
+                } else {
+                    index = GestureAddDollar(inTouch, path);
+                }
+
+                if (index >= 0) {
+                    GestureSendDollarRecord(inTouch, inTouch->dollarTemplate[index].hash);
+                } else {
+                    GestureSendDollarRecord(inTouch, -1);
+                }
+            } else {
+                int bestTempl = -1;
+                const float error = GestureDollarRecognize(&inTouch->dollarPath, &bestTempl, inTouch);
+                if (bestTempl >= 0) {
+                    /* Send Event */
+                    const unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
+                    GestureSendDollar(inTouch, gestureId, error);
+                    /* printf ("%s\n",);("Dollar error: %f\n",error); */
+                }
+            }
+
+            /* inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; */
+            if (inTouch->numDownFingers > 0) {
+                inTouch->centroid.x = (inTouch->centroid.x * (inTouch->numDownFingers + 1) - x) / inTouch->numDownFingers;
+                inTouch->centroid.y = (inTouch->centroid.y * (inTouch->numDownFingers + 1) - y) / inTouch->numDownFingers;
+            }
+        } else if (event->type == SDL_FINGERMOTION) {
+            const float dx = event->tfinger.dx;
+            const float dy = event->tfinger.dy;
+            GestureDollarPath *path = &inTouch->dollarPath;
+            if (path->numPoints < GESTURE_MAX_DOLLAR_PATH_SIZE) {
+                path->p[path->numPoints].x = inTouch->centroid.x;
+                path->p[path->numPoints].y = inTouch->centroid.y;
+                pathDx = (path->p[path->numPoints].x - path->p[path->numPoints - 1].x);
+                pathDy = (path->p[path->numPoints].y - path->p[path->numPoints - 1].y);
+                path->length += (float)SDL_sqrt(pathDx * pathDx + pathDy * pathDy);
+                path->numPoints++;
+            }
+
+            lastP.x = x - dx;
+            lastP.y = y - dy;
+            lastCentroid = inTouch->centroid;
+
+            inTouch->centroid.x += dx / inTouch->numDownFingers;
+            inTouch->centroid.y += dy / inTouch->numDownFingers;
+            /* printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */
+            if (inTouch->numDownFingers > 1) {
+                SDL_FPoint lv; /* Vector from centroid to last x,y position */
+                SDL_FPoint v;  /* Vector from centroid to current x,y position */
+                /* lv = inTouch->gestureLast[j].cv; */
+                lv.x = lastP.x - lastCentroid.x;
+                lv.y = lastP.y - lastCentroid.y;
+                lDist = SDL_sqrtf(lv.x * lv.x + lv.y * lv.y);
+                /* printf("lDist = %f\n",lDist); */
+                v.x = x - inTouch->centroid.x;
+                v.y = y - inTouch->centroid.y;
+                /* inTouch->gestureLast[j].cv = v; */
+                Dist = SDL_sqrtf(v.x * v.x + v.y * v.y);
+                /* SDL_cosf(dTheta) = (v . lv)/(|v| * |lv|) */
+
+                /* Normalize Vectors to simplify angle calculation */
+                lv.x /= lDist;
+                lv.y /= lDist;
+                v.x /= Dist;
+                v.y /= Dist;
+                dtheta = SDL_atan2f(lv.x * v.y - lv.y * v.x, lv.x * v.x + lv.y * v.y);
+
+                dDist = (Dist - lDist);
+                if (lDist == 0) {
+                    /* To avoid impossible values */
+                    dDist = 0;
+                    dtheta = 0;
+                }
+
+                /* inTouch->gestureLast[j].dDist = dDist;
+                inTouch->gestureLast[j].dtheta = dtheta;
+
+                printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
+                gdtheta = gdtheta*.9 + dtheta*.1;
+                gdDist  =  gdDist*.9 +  dDist*.1
+                knob.r += dDist/numDownFingers;
+                knob.ang += dtheta;
+                printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
+                printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */
+                GestureSendMulti(inTouch, dtheta, dDist);
+            } else {
+                /* inTouch->gestureLast[j].dDist = 0;
+                inTouch->gestureLast[j].dtheta = 0;
+                inTouch->gestureLast[j].cv.x = 0;
+                inTouch->gestureLast[j].cv.y = 0; */
+            }
+            /* inTouch->gestureLast[j].f.p.x = x;
+            inTouch->gestureLast[j].f.p.y = y;
+            break;
+            pressure? */
+        } else if (event->type == SDL_FINGERDOWN) {
+            inTouch->numDownFingers++;
+            inTouch->centroid.x = (inTouch->centroid.x * (inTouch->numDownFingers - 1) +
+                                   x) /
+                                  inTouch->numDownFingers;
+            inTouch->centroid.y = (inTouch->centroid.y * (inTouch->numDownFingers - 1) +
+                                   y) /
+                                  inTouch->numDownFingers;
+            /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
+                 inTouch->centroid.x,inTouch->centroid.y); */
+
+            inTouch->dollarPath.length = 0;
+            inTouch->dollarPath.p[0].x = x;
+            inTouch->dollarPath.p[0].y = y;
+            inTouch->dollarPath.numPoints = 1;
+        }
+    }
+}
+
+#endif  /* defined(SDL_GESTURE_IMPLEMENTATION) */
+#endif  /* SDL vesion > 2 

(Patch may be truncated, please check the link at the top of this post.)