2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
22 #include "../SDL_internal.h"
24 /* General gesture handling code for SDL */
26 #include "SDL_events.h"
27 #include "SDL_endian.h"
28 #include "SDL_events_c.h"
29 #include "SDL_gesture_c.h"
35 /* TODO: Replace with malloc */
37 #define MAXPATHSIZE 1024
41 #define DOLLARNPOINTS 64
43 #if defined(ENABLE_DOLLAR)
44 # define DOLLARSIZE 256
45 # define PHI 0.618033989
56 SDL_FloatPoint p[MAXPATHSIZE];
60 SDL_FloatPoint path[DOLLARNPOINTS];
66 SDL_FloatPoint centroid;
67 SDL_DollarPath dollarPath;
68 Uint16 numDownFingers;
70 int numDollarTemplates;
71 SDL_DollarTemplate *dollarTemplate;
76 static SDL_GestureTouch *SDL_gestureTouch;
77 static int SDL_numGestureTouches = 0;
78 static SDL_bool recordAll;
81 static void PrintPath(SDL_FloatPoint *path)
85 for (i=0; i<DOLLARNPOINTS; i++) {
86 printf(" (%f,%f)",path[i].x,path[i].y);
92 int SDL_RecordGesture(SDL_TouchID touchId)
95 if (touchId < 0) recordAll = SDL_TRUE;
96 for (i = 0; i < SDL_numGestureTouches; i++) {
97 if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
98 SDL_gestureTouch[i].recording = SDL_TRUE;
103 return (touchId < 0);
106 void SDL_GestureQuit()
108 SDL_free(SDL_gestureTouch);
109 SDL_gestureTouch = NULL;
112 static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
114 unsigned long hash = 5381;
116 for (i = 0; i < DOLLARNPOINTS; i++) {
117 hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
118 hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
124 static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops *dst)
130 /* No Longer storing the Hash, rehash on load */
131 /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash), 1) != 1) return 0; */
133 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
134 if (SDL_RWwrite(dst, templ->path,
135 sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS) {
140 SDL_DollarTemplate copy = *templ;
141 SDL_FloatPoint *p = copy.path;
143 for (i = 0; i < DOLLARNPOINTS; i++, p++) {
144 p->x = SDL_SwapFloatLE(p->x);
145 p->y = SDL_SwapFloatLE(p->y);
148 if (SDL_RWwrite(dst, copy.path,
149 sizeof(copy.path[0]),DOLLARNPOINTS) != DOLLARNPOINTS) {
159 int SDL_SaveAllDollarTemplates(SDL_RWops *dst)
162 for (i = 0; i < SDL_numGestureTouches; i++) {
163 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
164 for (j = 0; j < touch->numDollarTemplates; j++) {
165 rtrn += SaveTemplate(&touch->dollarTemplate[j], dst);
171 int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst)
174 for (i = 0; i < SDL_numGestureTouches; i++) {
175 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
176 for (j = 0; j < touch->numDollarTemplates; j++) {
177 if (touch->dollarTemplate[j].hash == gestureId) {
178 return SaveTemplate(&touch->dollarTemplate[j], dst);
182 return SDL_SetError("Unknown gestureId");
185 /* path is an already sampled set of points
186 Returns the index of the gesture on success, or -1 */
187 static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
189 SDL_DollarTemplate* dollarTemplate;
190 SDL_DollarTemplate *templ;
193 index = inTouch->numDollarTemplates;
195 (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
197 sizeof(SDL_DollarTemplate));
198 if (!dollarTemplate) {
199 return SDL_OutOfMemory();
201 inTouch->dollarTemplate = dollarTemplate;
203 templ = &inTouch->dollarTemplate[index];
204 SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
205 templ->hash = SDL_HashDollar(templ->path);
206 inTouch->numDollarTemplates++;
211 static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
215 if (inTouch == NULL) {
216 if (SDL_numGestureTouches == 0) return SDL_SetError("no gesture touch devices registered");
217 for (i = 0; i < SDL_numGestureTouches; i++) {
218 inTouch = &SDL_gestureTouch[i];
219 index = SDL_AddDollarGesture_one(inTouch, path);
223 /* Use the index of the last one added. */
226 return SDL_AddDollarGesture_one(inTouch, path);
229 int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
232 SDL_GestureTouch *touch = NULL;
233 if (src == NULL) return 0;
235 for (i = 0; i < SDL_numGestureTouches; i++) {
236 if (SDL_gestureTouch[i].id == touchId) {
237 touch = &SDL_gestureTouch[i];
241 return SDL_SetError("given touch id not found");
246 SDL_DollarTemplate templ;
248 if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) < DOLLARNPOINTS) {
250 return SDL_SetError("could not read any dollar gesture from rwops");
255 #if SDL_BYTEORDER != SDL_LIL_ENDIAN
256 for (i = 0; i < DOLLARNPOINTS; i++) {
257 SDL_FloatPoint *p = &templ.path[i];
258 p->x = SDL_SwapFloatLE(p->x);
259 p->y = SDL_SwapFloatLE(p->y);
264 /* printf("Adding loaded gesture to 1 touch\n"); */
265 if (SDL_AddDollarGesture(touch, templ.path) >= 0)
269 /* printf("Adding to: %i touches\n",SDL_numGestureTouches); */
270 for (i = 0; i < SDL_numGestureTouches; i++) {
271 touch = &SDL_gestureTouch[i];
272 /* printf("Adding loaded gesture to + touches\n"); */
273 /* TODO: What if this fails? */
274 SDL_AddDollarGesture(touch,templ.path);
284 #if defined(ENABLE_DOLLAR)
285 static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
287 /* SDL_FloatPoint p[DOLLARNPOINTS]; */
291 for (i = 0; i < DOLLARNPOINTS; i++) {
292 p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
293 p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
294 dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
295 (p.y-templ[i].y)*(p.y-templ[i].y)));
297 return dist/DOLLARNPOINTS;
301 static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
303 /*------------BEGIN DOLLAR BLACKBOX------------------
304 -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-
305 -"http://depts.washington.edu/aimgroup/proj/dollar/"
310 float x1 = (float)(PHI*ta + (1-PHI)*tb);
311 float f1 = dollarDifference(points,templ,x1);
312 float x2 = (float)((1-PHI)*ta + PHI*tb);
313 float f2 = dollarDifference(points,templ,x2);
314 while (SDL_fabs(ta-tb) > dt) {
319 x1 = (float)(PHI*ta + (1-PHI)*tb);
320 f1 = dollarDifference(points,templ,x1);
326 x2 = (float)((1-PHI)*ta + PHI*tb);
327 f2 = dollarDifference(points,templ,x2);
332 printf("Min angle (x1): %f\n",x1);
334 printf("Min angle (x2): %f\n",x2);
336 return SDL_min(f1,f2);
339 /* DollarPath contains raw points, plus (possibly) the calculated length */
340 static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points, SDL_bool is_recording)
346 SDL_FloatPoint centroid;
347 float xmin,xmax,ymin,ymax;
350 float length = path->length;
352 /* Calculate length if it hasn't already been done */
354 for (i=1;i < path->numPoints; i++) {
355 float dx = path->p[i ].x - path->p[i-1].x;
356 float dy = path->p[i ].y - path->p[i-1].y;
357 length += (float)(SDL_sqrt(dx*dx+dy*dy));
362 interval = length/(DOLLARNPOINTS - 1);
365 centroid.x = 0;centroid.y = 0;
367 /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); */
368 for (i = 1; i < path->numPoints; i++) {
369 float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
370 (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
371 /* printf("d = %f dist = %f/%f\n",d,dist,interval); */
372 while (dist + d > interval) {
373 points[numPoints].x = path->p[i-1].x +
374 ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
375 points[numPoints].y = path->p[i-1].y +
376 ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
377 centroid.x += points[numPoints].x;
378 centroid.y += points[numPoints].y;
385 if (numPoints < DOLLARNPOINTS-1) {
387 SDL_SetError("ERROR: NumPoints = %i", numPoints);
391 /* copy the last point */
392 points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
393 numPoints = DOLLARNPOINTS;
395 centroid.x /= numPoints;
396 centroid.y /= numPoints;
398 /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */
399 /* Rotate Points so point 0 is left of centroid and solve for the bounding box */
405 ang = (float)(SDL_atan2(centroid.y - points[0].y,
406 centroid.x - points[0].x));
408 for (i = 0; i<numPoints; i++) {
409 float px = points[i].x;
410 float py = points[i].y;
411 points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
412 (py - centroid.y)*SDL_sin(ang) + centroid.x);
413 points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
414 (py - centroid.y)*SDL_cos(ang) + centroid.y);
417 if (points[i].x < xmin) xmin = points[i].x;
418 if (points[i].x > xmax) xmax = points[i].x;
419 if (points[i].y < ymin) ymin = points[i].y;
420 if (points[i].y > ymax) ymax = points[i].y;
423 /* Scale points to DOLLARSIZE, and translate to the origin */
427 for (i=0; i<numPoints; i++) {
428 points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
429 points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
434 static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
436 SDL_FloatPoint points[DOLLARNPOINTS];
438 float bestDiff = 10000;
440 SDL_memset(points, 0, sizeof(points));
442 dollarNormalize(path, points, SDL_FALSE);
444 /* PrintPath(points); */
446 for (i = 0; i < touch->numDollarTemplates; i++) {
447 float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
448 if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
454 int SDL_GestureAddTouch(SDL_TouchID touchId)
456 SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
457 (SDL_numGestureTouches + 1) *
458 sizeof(SDL_GestureTouch));
461 return SDL_OutOfMemory();
464 SDL_gestureTouch = gestureTouch;
466 SDL_zero(SDL_gestureTouch[SDL_numGestureTouches]);
467 SDL_gestureTouch[SDL_numGestureTouches].id = touchId;
468 SDL_numGestureTouches++;
472 int SDL_GestureDelTouch(SDL_TouchID touchId)
475 for (i = 0; i < SDL_numGestureTouches; i++) {
476 if (SDL_gestureTouch[i].id == touchId) {
481 if (i == SDL_numGestureTouches) {
486 SDL_free(SDL_gestureTouch[i].dollarTemplate);
487 SDL_zero(SDL_gestureTouch[i]);
489 SDL_numGestureTouches--;
490 SDL_memcpy(&SDL_gestureTouch[i], &SDL_gestureTouch[SDL_numGestureTouches], sizeof(SDL_gestureTouch[i]));
494 static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
497 for (i = 0; i < SDL_numGestureTouches; i++) {
498 /* printf("%i ?= %i\n",SDL_gestureTouch[i].id,id); */
499 if (SDL_gestureTouch[i].id == id)
500 return &SDL_gestureTouch[i];
505 static void SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
507 if (SDL_GetEventState(SDL_MULTIGESTURE) == SDL_ENABLE) {
509 event.mgesture.type = SDL_MULTIGESTURE;
510 event.mgesture.touchId = touch->id;
511 event.mgesture.x = touch->centroid.x;
512 event.mgesture.y = touch->centroid.y;
513 event.mgesture.dTheta = dTheta;
514 event.mgesture.dDist = dDist;
515 event.mgesture.numFingers = touch->numDownFingers;
516 SDL_PushEvent(&event);
520 #if defined(ENABLE_DOLLAR)
521 static void SDL_SendGestureDollar(SDL_GestureTouch* touch,
522 SDL_GestureID gestureId,float error)
524 if (SDL_GetEventState(SDL_DOLLARGESTURE) == SDL_ENABLE) {
526 event.dgesture.type = SDL_DOLLARGESTURE;
527 event.dgesture.touchId = touch->id;
528 event.dgesture.x = touch->centroid.x;
529 event.dgesture.y = touch->centroid.y;
530 event.dgesture.gestureId = gestureId;
531 event.dgesture.error = error;
532 /* A finger came up to trigger this event. */
533 event.dgesture.numFingers = touch->numDownFingers + 1;
534 SDL_PushEvent(&event);
538 static void SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
540 if (SDL_GetEventState(SDL_DOLLARRECORD) == SDL_ENABLE) {
542 event.dgesture.type = SDL_DOLLARRECORD;
543 event.dgesture.touchId = touch->id;
544 event.dgesture.gestureId = gestureId;
545 SDL_PushEvent(&event);
551 void SDL_GestureProcessEvent(SDL_Event* event)
554 #if defined(ENABLE_DOLLAR)
557 float pathDx, pathDy;
559 SDL_FloatPoint lastP;
560 SDL_FloatPoint lastCentroid;
566 if (event->type == SDL_FINGERMOTION ||
567 event->type == SDL_FINGERDOWN ||
568 event->type == SDL_FINGERUP) {
569 SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
571 /* Shouldn't be possible */
572 if (inTouch == NULL) return;
574 x = event->tfinger.x;
575 y = event->tfinger.y;
578 if (event->type == SDL_FINGERUP) {
579 #if defined(ENABLE_DOLLAR)
580 SDL_FloatPoint path[DOLLARNPOINTS];
583 inTouch->numDownFingers--;
585 #if defined(ENABLE_DOLLAR)
586 if (inTouch->recording) {
587 inTouch->recording = SDL_FALSE;
588 dollarNormalize(&inTouch->dollarPath, path, SDL_TRUE);
589 /* PrintPath(path); */
591 index = SDL_AddDollarGesture(NULL,path);
592 for (i = 0; i < SDL_numGestureTouches; i++)
593 SDL_gestureTouch[i].recording = SDL_FALSE;
596 index = SDL_AddDollarGesture(inTouch,path);
600 SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
603 SDL_SendDollarRecord(inTouch,-1);
609 error = dollarRecognize(&inTouch->dollarPath,
613 unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
614 SDL_SendGestureDollar(inTouch,gestureId,error);
615 /* printf ("%s\n",);("Dollar error: %f\n",error); */
619 /* inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; */
620 if (inTouch->numDownFingers > 0) {
621 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
622 x)/inTouch->numDownFingers;
623 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
624 y)/inTouch->numDownFingers;
627 else if (event->type == SDL_FINGERMOTION) {
628 float dx = event->tfinger.dx;
629 float dy = event->tfinger.dy;
630 #if defined(ENABLE_DOLLAR)
631 SDL_DollarPath* path = &inTouch->dollarPath;
632 if (path->numPoints < MAXPATHSIZE) {
633 path->p[path->numPoints].x = inTouch->centroid.x;
634 path->p[path->numPoints].y = inTouch->centroid.y;
636 (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
638 (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
639 path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
645 lastCentroid = inTouch->centroid;
647 inTouch->centroid.x += dx/inTouch->numDownFingers;
648 inTouch->centroid.y += dy/inTouch->numDownFingers;
649 /* printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */
650 if (inTouch->numDownFingers > 1) {
651 SDL_FloatPoint lv; /* Vector from centroid to last x,y position */
652 SDL_FloatPoint v; /* Vector from centroid to current x,y position */
653 /* lv = inTouch->gestureLast[j].cv; */
654 lv.x = lastP.x - lastCentroid.x;
655 lv.y = lastP.y - lastCentroid.y;
656 lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
657 /* printf("lDist = %f\n",lDist); */
658 v.x = x - inTouch->centroid.x;
659 v.y = y - inTouch->centroid.y;
660 /* inTouch->gestureLast[j].cv = v; */
661 Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
662 /* SDL_cos(dTheta) = (v . lv)/(|v| * |lv|) */
664 /* Normalize Vectors to simplify angle calculation */
669 dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
671 dDist = (Dist - lDist);
672 if (lDist == 0) {dDist = 0;dtheta = 0;} /* To avoid impossible values */
674 /* inTouch->gestureLast[j].dDist = dDist;
675 inTouch->gestureLast[j].dtheta = dtheta;
677 printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
678 gdtheta = gdtheta*.9 + dtheta*.1;
679 gdDist = gdDist*.9 + dDist*.1
680 knob.r += dDist/numDownFingers;
682 printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
683 printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */
684 SDL_SendGestureMulti(inTouch,dtheta,dDist);
687 /* inTouch->gestureLast[j].dDist = 0;
688 inTouch->gestureLast[j].dtheta = 0;
689 inTouch->gestureLast[j].cv.x = 0;
690 inTouch->gestureLast[j].cv.y = 0; */
692 /* inTouch->gestureLast[j].f.p.x = x;
693 inTouch->gestureLast[j].f.p.y = y;
697 else if (event->type == SDL_FINGERDOWN) {
699 inTouch->numDownFingers++;
700 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
701 x)/inTouch->numDownFingers;
702 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
703 y)/inTouch->numDownFingers;
704 /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
705 inTouch->centroid.x,inTouch->centroid.y); */
707 #if defined(ENABLE_DOLLAR)
708 inTouch->dollarPath.length = 0;
709 inTouch->dollarPath.p[0].x = x;
710 inTouch->dollarPath.p[0].y = y;
711 inTouch->dollarPath.numPoints = 1;
717 /* vi: set ts=4 sw=4 expandtab: */