sdl2-compat: gesture: Migrated the SDL2 gesture code out of SDL3 and into here.

From d14dac9da48ea59f30dc272029c9d3a0972e6683 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 13 Dec 2022 14:46:42 -0500
Subject: [PATCH] gesture: Migrated the SDL2 gesture code out of SDL3 and into
 here.

---
 src/dynapi/SDL_dynapi.c |   4 +
 src/sdl2_compat.c       | 685 +++++++++++++++++++++++++++++++++++++++-
 src/sdl3_syms.h         |   5 +-
 3 files changed, 689 insertions(+), 5 deletions(-)

diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index 5e5af05..156c991 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -27,6 +27,10 @@
 
 #define SDL_DYNAMIC_API_ENVVAR "SDL_DYNAMIC_API"
 
+/* sdl2-compat: this type was removed from SDL3, but we need it for SDL2 APIs exported here. */
+typedef Sint64 SDL_GestureID;
+
+
 #if defined(__OS2__)
 #define INCL_DOS
 #define INCL_DOSERRORS
diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 556586d..5c81a1f 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -99,6 +99,8 @@
 extern "C" {
 #endif
 
+typedef Sint64 SDL_GestureID;
+
 #define SDL3_SYM(rc,fn,params,args,ret) \
     typedef rc (SDLCALL *SDL3_##fn##_t) params; \
     static SDL3_##fn##_t SDL3_##fn = NULL;
@@ -116,6 +118,9 @@ extern "C" {
 #define SDL3_InvalidParamError(param) SDL3_SetError("Parameter '%s' is invalid", (param))
 #define SDL3_zero(x) SDL3_memset(&(x), 0, sizeof((x)))
 #define SDL3_zerop(x) SDL3_memset((x), 0, sizeof(*(x)))
+#define SDL3_copyp(dst, src)                                                                 \
+    { SDL_COMPILE_TIME_ASSERT(SDL3_copyp, sizeof (*(dst)) == sizeof (*(src))); }             \
+    SDL3_memcpy((dst), (src), sizeof (*(src)))
 
 
 static SDL_bool WantDebugLogging = SDL_FALSE;
@@ -512,11 +517,12 @@ typedef enum
     SDL2_SYSWM_RISCOS
 } SDL2_SYSWM_TYPE;
 
+
 /* Events changed in SDL3; notably, the `timestamp` field moved from
    32 bit milliseconds to 64-bit nanoseconds, and the padding of the union
    changed, so all the SDL2 structs have to be reproduced here. */
 
-/* Note that SDL_EventType _currently_ lines up (although some types have
+/* Note that SDL_EventType _currently_ lines up; although some types have
    come and gone in SDL3, so we don't manage an SDL2 copy here atm. */
 
 typedef struct SDL2_CommonEvent
@@ -778,6 +784,10 @@ typedef struct SDL2_TouchFingerEvent
     Uint32 windowID;
 } SDL2_TouchFingerEvent;
 
+#define SDL_DOLLARGESTURE 0x800
+#define SDL_DOLLARRECORD 0x801
+#define SDL_MULTIGESTURE 0x802
+
 typedef struct SDL2_MultiGestureEvent
 {
     Uint32 type;
@@ -1124,10 +1134,15 @@ Event2to3(const SDL2_Event *event2, SDL_Event *event3)
     return event3;
 }
 
+static void GestureProcessEvent(const SDL_Event *event3);
+
 static int SDLCALL
 EventFilter3to2(void *userdata, SDL_Event *event3)
 {
     SDL2_Event event2;  /* note that event filters do not receive events as const! So we have to convert or copy it for each one! */
+
+    GestureProcessEvent(event3);  /* this might need to generate new gesture events from touch input. */
+
     if (EventFilter2) {
         return EventFilter2(EventFilterUserData2, Event3to2(event3, &event2));
     }
@@ -1584,6 +1599,674 @@ SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *dat
     return SDL3_Unsupported();  /* !!! FIXME: maybe try to track this from SDL3 events if something needs this? I can't imagine this was widely used. */
 }
 
+
+/* Touch gestures were removed from SDL3, so this is the SDL2 implementation copied in here, and tweaked a little. */
+
+#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 GestureTouch *GestureAddTouch(const SDL_TouchID touchId)
+{
+    GestureTouch *gestureTouch = (GestureTouch *)SDL3_realloc(GestureTouches, (GestureNumTouches + 1) * sizeof(GestureTouch));
+    if (gestureTouch == NULL) {
+        SDL3_OutOfMemory();
+        return NULL;
+    }
+
+    GestureTouches = gestureTouch;
+    SDL3_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;
+    }
+
+    SDL3_free(GestureTouches[i].dollarTemplate);
+    SDL3_zero(GestureTouches[i]);
+
+    GestureNumTouches--;
+    if (i != GestureNumTouches) {
+        SDL3_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;
+}
+
+DECLSPEC int SDLCALL
+SDL_RecordGesture(SDL_TouchID touchId)
+{
+    const int numtouchdevs = SDL3_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 = SDL3_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;
+}
+
+/* !!! FIXME: we need to hook this up when we override SDL_Quit */
+static void GestureQuit(void)
+{
+    SDL3_free(GestureTouches);
+    GestureTouches = NULL;
+}
+
+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)
+{
+    if (dst == NULL) {
+        return 0;
+    }
+
+    /* No Longer storing the Hash, rehash on load */
+    /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash), 1) != 1) return 0; */
+
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+    if (SDL3_RWwrite(dst, templ->path, sizeof(templ->path[0]), GESTURE_DOLLARNPOINTS) != GESTURE_DOLLARNPOINTS) {
+        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 (SDL3_RWwrite(dst, copy.path, sizeof(copy.path[0]), GESTURE_DOLLARNPOINTS) != GESTURE_DOLLARNPOINTS) {
+            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 SDL3_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 *)SDL3_realloc(inTouch->dollarTemplate, (index + 1) * sizeof(GestureDollarTemplate));
+    if (dollarTemplate == NULL) {
+        return SDL3_OutOfMemory();
+    }
+    inTouch->dollarTemplate = dollarTemplate;
+
+    templ = &inTouch->dollarTemplate[index];
+    SDL3_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 SDL3_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 SDL3_SetError("given touch id not found");
+        }
+    }
+
+    while (1) {
+        GestureDollarTemplate templ;
+
+        if (SDL3_RWread(src, templ.path, sizeof(templ.path[0]), GESTURE_DOLLARNPOINTS) < GESTURE_DOLLARNPOINTS) {
+            if (loaded == 0) {
+                return SDL3_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 * SDL3_cosf(ang) - points[i].y * SDL_sinf(ang);
+        p.y = points[i].x * SDL3_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 (SDL3_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 += SDL3_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 = SDL3_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) {
+            SDL3_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 = SDL3_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) * SDL3_cosf(ang) - (py - centroid.y) * SDL3_sinf(ang) + centroid.x;
+        points[i].y = (px - centroid.x) * SDL3_sinf(ang) + (py - centroid.y) * SDL3_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;
+
+    SDL3_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 (SDL3_GetEventState(SDL_MULTIGESTURE) == SDL_ENABLE) {
+        SDL2_Event event;
+        event.type = SDL_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 (SDL3_GetEventState(SDL_DOLLARGESTURE) == SDL_ENABLE) {
+        SDL2_Event event;
+        event.type = SDL_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 (SDL3_GetEventState(SDL_DOLLARRECORD) == SDL_ENABLE) {
+        SDL2_Event event;
+        event.type = SDL_DOLLARRECORD;
+        event.common.timestamp = 0;
+        event.dgesture.touchId = touch->touchId;
+        event.dgesture.gestureId = gestureId;
+        SDL_PushEvent(&event);
+    }
+}
+
+/* These are SDL3 events coming in from sdl2-compat's event watcher. */
+static void GestureProcessEvent(const SDL_Event *event3)
+{
+    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 (event3->type == SDL_FINGERMOTION || event3->type == SDL_FINGERDOWN || event3->type == SDL_FINGERUP) {
+        GestureTouch *inTouch = GestureGetTouch(event3->tfinger.touchId);
+
+        if (inTouch == NULL) {  /* we maybe didn't see this one before. */
+            inTouch = GestureAddTouch(event3->tfinger.touchId);
+            if (!inTouch) {
+                return;  /* oh well. */
+            }
+        }
+
+        x = event3->tfinger.x;
+        y = event3->tfinger.y;
+
+        /* Finger Up */
+        if (event3->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 (event3->type == SDL_FINGERMOTION) {
+            const float dx = event3->tfinger.dx;
+            const float dy = event3->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)SDL3_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 = SDL3_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 = SDL3_sqrtf(v.x * v.x + v.y * v.y);
+                /* SDL3_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 = SDL3_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 (event3->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;
+        }
+    }
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index 577be81..07a5cfb 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -175,10 +175,6 @@ SDL3_SYM_PASSTHROUGH(const char*,GameControllerGetStringForButton,(SDL_GameContr
 SDL3_SYM_PASSTHROUGH(SDL_GameControllerButtonBind,GameControllerGetBindForButton,(SDL_GameController *a, SDL_GameControllerButton b),(a,b),return)
 SDL3_SYM_PASSTHROUGH(Uint8,GameControllerGetButton,(SDL_GameController *a, SDL_GameControllerButton b),(a,b),return)
 SDL3_SYM_PASSTHROUGH(void,GameControllerClose,(SDL_GameController *a),(a),)
-SDL3_SYM_PASSTHROUGH(int,RecordGesture,(SDL_TouchID a),(a),return)
-SDL3_SYM_PASSTHROUGH(int,SaveAllDollarTemplates,(SDL_RWops *a),(a),return)
-SDL3_SYM_PASSTHROUGH(int,SaveDollarTemplate,(SDL_GestureID a, SDL_RWops *b),(a,b),return)
-SDL3_SYM_PASSTHROUGH(int,LoadDollarTemplates,(SDL_TouchID a, SDL_RWops *b),(a,b),return)
 SDL3_SYM_PASSTHROUGH(int,NumHaptics,(void),(),return)
 SDL3_SYM_PASSTHROUGH(const char*,HapticName,(int a),(a),return)
 SDL3_SYM_PASSTHROUGH(SDL_Haptic*,HapticOpen,(int a),(a),return)
@@ -944,6 +940,7 @@ SDL3_SYM_PASSTHROUGH(char*,GetPrimarySelectionText,(void),(),return)
 SDL3_SYM_PASSTHROUGH(SDL_bool,HasPrimarySelectionText,(void),(),return)
 SDL3_SYM_PASSTHROUGH(void,ResetHints,(void),(),)
 SDL3_SYM_PASSTHROUGH(char*,strcasestr,(const char *a, const char *b),(a,b),return)
+SDL3_SYM_PASSTHROUGH(Uint8,GetEventState,(Uint32 a),(a),return)
 
 #undef SDL3_SYM
 #undef SDL3_SYM_PASSTHROUGH