2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 mouse 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
39 #define DOLLARNPOINTS 64
40 #define DOLLARSIZE 256
44 #define PHI 0.618033989
54 SDL_FloatPoint p[MAXPATHSIZE];
58 SDL_FloatPoint path[DOLLARNPOINTS];
64 SDL_FloatPoint centroid;
65 SDL_DollarPath dollarPath;
66 Uint16 numDownFingers;
68 int numDollarTemplates;
69 SDL_DollarTemplate *dollarTemplate;
74 SDL_GestureTouch *SDL_gestureTouch;
75 int SDL_numGestureTouches = 0;
79 static void PrintPath(SDL_FloatPoint *path)
83 for (i=0; i<DOLLARNPOINTS; i++) {
84 printf(" (%f,%f)",path[i].x,path[i].y);
90 int SDL_RecordGesture(SDL_TouchID touchId)
93 if (touchId < 0) recordAll = SDL_TRUE;
94 for (i = 0; i < SDL_numGestureTouches; i++) {
95 if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
96 SDL_gestureTouch[i].recording = SDL_TRUE;
101 return (touchId < 0);
104 static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
106 unsigned long hash = 5381;
108 for (i = 0; i < DOLLARNPOINTS; i++) {
109 hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
110 hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
116 static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops *dst)
122 /* No Longer storing the Hash, rehash on load */
123 /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash), 1) != 1) return 0; */
125 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
126 if (SDL_RWwrite(dst, templ->path,
127 sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS) {
132 SDL_DollarTemplate copy = *templ;
133 SDL_FloatPoint *p = copy.path;
135 for (i = 0; i < DOLLARNPOINTS; i++, p++) {
136 p->x = SDL_SwapFloatLE(p->x);
137 p->y = SDL_SwapFloatLE(p->y);
140 if (SDL_RWwrite(dst, copy.path,
141 sizeof(copy.path[0]),DOLLARNPOINTS) != DOLLARNPOINTS) {
151 int SDL_SaveAllDollarTemplates(SDL_RWops *dst)
154 for (i = 0; i < SDL_numGestureTouches; i++) {
155 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
156 for (j = 0; j < touch->numDollarTemplates; j++) {
157 rtrn += SaveTemplate(&touch->dollarTemplate[j], dst);
163 int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst)
166 for (i = 0; i < SDL_numGestureTouches; i++) {
167 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
168 for (j = 0; j < touch->numDollarTemplates; j++) {
169 if (touch->dollarTemplate[j].hash == gestureId) {
170 return SaveTemplate(&touch->dollarTemplate[j], dst);
174 return SDL_SetError("Unknown gestureId");
177 /* path is an already sampled set of points
178 Returns the index of the gesture on success, or -1 */
179 static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
181 SDL_DollarTemplate* dollarTemplate;
182 SDL_DollarTemplate *templ;
185 index = inTouch->numDollarTemplates;
187 (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
189 sizeof(SDL_DollarTemplate));
190 if (!dollarTemplate) {
191 return SDL_OutOfMemory();
193 inTouch->dollarTemplate = dollarTemplate;
195 templ = &inTouch->dollarTemplate[index];
196 SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
197 templ->hash = SDL_HashDollar(templ->path);
198 inTouch->numDollarTemplates++;
203 static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
207 if (inTouch == NULL) {
208 if (SDL_numGestureTouches == 0) return SDL_SetError("no gesture touch devices registered");
209 for (i = 0; i < SDL_numGestureTouches; i++) {
210 inTouch = &SDL_gestureTouch[i];
211 index = SDL_AddDollarGesture_one(inTouch, path);
215 /* Use the index of the last one added. */
218 return SDL_AddDollarGesture_one(inTouch, path);
221 int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
224 SDL_GestureTouch *touch = NULL;
225 if (src == NULL) return 0;
227 for (i = 0; i < SDL_numGestureTouches; i++) {
228 if (SDL_gestureTouch[i].id == touchId) {
229 touch = &SDL_gestureTouch[i];
233 return SDL_SetError("given touch id not found");
238 SDL_DollarTemplate templ;
240 if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) < DOLLARNPOINTS) {
242 return SDL_SetError("could not read any dollar gesture from rwops");
247 #if SDL_BYTEORDER != SDL_LIL_ENDIAN
248 for (i = 0; i < DOLLARNPOINTS; i++) {
249 SDL_FloatPoint *p = &templ.path[i];
250 p->x = SDL_SwapFloatLE(p->x);
251 p->y = SDL_SwapFloatLE(p->y);
256 /* printf("Adding loaded gesture to 1 touch\n"); */
257 if (SDL_AddDollarGesture(touch, templ.path) >= 0)
261 /* printf("Adding to: %i touches\n",SDL_numGestureTouches); */
262 for (i = 0; i < SDL_numGestureTouches; i++) {
263 touch = &SDL_gestureTouch[i];
264 /* printf("Adding loaded gesture to + touches\n"); */
265 /* TODO: What if this fails? */
266 SDL_AddDollarGesture(touch,templ.path);
276 static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
278 /* SDL_FloatPoint p[DOLLARNPOINTS]; */
282 for (i = 0; i < DOLLARNPOINTS; i++) {
283 p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
284 p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
285 dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
286 (p.y-templ[i].y)*(p.y-templ[i].y)));
288 return dist/DOLLARNPOINTS;
292 static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
294 /*------------BEGIN DOLLAR BLACKBOX------------------
295 -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-
296 -"http://depts.washington.edu/aimgroup/proj/dollar/"
301 float x1 = (float)(PHI*ta + (1-PHI)*tb);
302 float f1 = dollarDifference(points,templ,x1);
303 float x2 = (float)((1-PHI)*ta + PHI*tb);
304 float f2 = dollarDifference(points,templ,x2);
305 while (SDL_fabs(ta-tb) > dt) {
310 x1 = (float)(PHI*ta + (1-PHI)*tb);
311 f1 = dollarDifference(points,templ,x1);
317 x2 = (float)((1-PHI)*ta + PHI*tb);
318 f2 = dollarDifference(points,templ,x2);
323 printf("Min angle (x1): %f\n",x1);
325 printf("Min angle (x2): %f\n",x2);
327 return SDL_min(f1,f2);
330 /* DollarPath contains raw points, plus (possibly) the calculated length */
331 static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points)
337 SDL_FloatPoint centroid;
338 float xmin,xmax,ymin,ymax;
341 float length = path->length;
343 /* Calculate length if it hasn't already been done */
345 for (i=1;i < path->numPoints; i++) {
346 float dx = path->p[i ].x - path->p[i-1].x;
347 float dy = path->p[i ].y - path->p[i-1].y;
348 length += (float)(SDL_sqrt(dx*dx+dy*dy));
353 interval = length/(DOLLARNPOINTS - 1);
356 centroid.x = 0;centroid.y = 0;
358 /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); */
359 for (i = 1; i < path->numPoints; i++) {
360 float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
361 (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
362 /* printf("d = %f dist = %f/%f\n",d,dist,interval); */
363 while (dist + d > interval) {
364 points[numPoints].x = path->p[i-1].x +
365 ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
366 points[numPoints].y = path->p[i-1].y +
367 ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
368 centroid.x += points[numPoints].x;
369 centroid.y += points[numPoints].y;
376 if (numPoints < DOLLARNPOINTS-1) {
377 SDL_SetError("ERROR: NumPoints = %i\n",numPoints);
380 /* copy the last point */
381 points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
382 numPoints = DOLLARNPOINTS;
384 centroid.x /= numPoints;
385 centroid.y /= numPoints;
387 /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */
388 /* Rotate Points so point 0 is left of centroid and solve for the bounding box */
394 ang = (float)(SDL_atan2(centroid.y - points[0].y,
395 centroid.x - points[0].x));
397 for (i = 0; i<numPoints; i++) {
398 float px = points[i].x;
399 float py = points[i].y;
400 points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
401 (py - centroid.y)*SDL_sin(ang) + centroid.x);
402 points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
403 (py - centroid.y)*SDL_cos(ang) + centroid.y);
406 if (points[i].x < xmin) xmin = points[i].x;
407 if (points[i].x > xmax) xmax = points[i].x;
408 if (points[i].y < ymin) ymin = points[i].y;
409 if (points[i].y > ymax) ymax = points[i].y;
412 /* Scale points to DOLLARSIZE, and translate to the origin */
416 for (i=0; i<numPoints; i++) {
417 points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
418 points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
423 static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
425 SDL_FloatPoint points[DOLLARNPOINTS];
427 float bestDiff = 10000;
429 SDL_memset(points, 0, sizeof(points));
431 dollarNormalize(path,points);
433 /* PrintPath(points); */
435 for (i = 0; i < touch->numDollarTemplates; i++) {
436 float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
437 if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
442 int SDL_GestureAddTouch(SDL_TouchID touchId)
444 SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
445 (SDL_numGestureTouches + 1) *
446 sizeof(SDL_GestureTouch));
449 return SDL_OutOfMemory();
452 SDL_gestureTouch = gestureTouch;
454 SDL_zero(SDL_gestureTouch[SDL_numGestureTouches]);
455 SDL_gestureTouch[SDL_numGestureTouches].id = touchId;
456 SDL_numGestureTouches++;
460 static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
463 for (i = 0; i < SDL_numGestureTouches; i++) {
464 /* printf("%i ?= %i\n",SDL_gestureTouch[i].id,id); */
465 if (SDL_gestureTouch[i].id == id)
466 return &SDL_gestureTouch[i];
471 int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
474 event.mgesture.type = SDL_MULTIGESTURE;
475 event.mgesture.touchId = touch->id;
476 event.mgesture.x = touch->centroid.x;
477 event.mgesture.y = touch->centroid.y;
478 event.mgesture.dTheta = dTheta;
479 event.mgesture.dDist = dDist;
480 event.mgesture.numFingers = touch->numDownFingers;
481 return SDL_PushEvent(&event) > 0;
484 static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
485 SDL_GestureID gestureId,float error)
488 event.dgesture.type = SDL_DOLLARGESTURE;
489 event.dgesture.touchId = touch->id;
490 event.dgesture.x = touch->centroid.x;
491 event.dgesture.y = touch->centroid.y;
492 event.dgesture.gestureId = gestureId;
493 event.dgesture.error = error;
494 /* A finger came up to trigger this event. */
495 event.dgesture.numFingers = touch->numDownFingers + 1;
496 return SDL_PushEvent(&event) > 0;
500 static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
503 event.dgesture.type = SDL_DOLLARRECORD;
504 event.dgesture.touchId = touch->id;
505 event.dgesture.gestureId = gestureId;
506 return SDL_PushEvent(&event) > 0;
510 void SDL_GestureProcessEvent(SDL_Event* event)
515 float pathDx, pathDy;
516 SDL_FloatPoint lastP;
517 SDL_FloatPoint lastCentroid;
523 if (event->type == SDL_FINGERMOTION ||
524 event->type == SDL_FINGERDOWN ||
525 event->type == SDL_FINGERUP) {
526 SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
528 /* Shouldn't be possible */
529 if (inTouch == NULL) return;
531 x = event->tfinger.x;
532 y = event->tfinger.y;
535 if (event->type == SDL_FINGERUP) {
536 SDL_FloatPoint path[DOLLARNPOINTS];
538 inTouch->numDownFingers--;
541 if (inTouch->recording) {
542 inTouch->recording = SDL_FALSE;
543 dollarNormalize(&inTouch->dollarPath,path);
544 /* PrintPath(path); */
546 index = SDL_AddDollarGesture(NULL,path);
547 for (i = 0; i < SDL_numGestureTouches; i++)
548 SDL_gestureTouch[i].recording = SDL_FALSE;
551 index = SDL_AddDollarGesture(inTouch,path);
555 SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
558 SDL_SendDollarRecord(inTouch,-1);
564 error = dollarRecognize(&inTouch->dollarPath,
568 unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
569 SDL_SendGestureDollar(inTouch,gestureId,error);
570 /* printf ("%s\n",);("Dollar error: %f\n",error); */
574 /* inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; */
575 if (inTouch->numDownFingers > 0) {
576 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
577 x)/inTouch->numDownFingers;
578 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
579 y)/inTouch->numDownFingers;
582 else if (event->type == SDL_FINGERMOTION) {
583 float dx = event->tfinger.dx;
584 float dy = event->tfinger.dy;
586 SDL_DollarPath* path = &inTouch->dollarPath;
587 if (path->numPoints < MAXPATHSIZE) {
588 path->p[path->numPoints].x = inTouch->centroid.x;
589 path->p[path->numPoints].y = inTouch->centroid.y;
591 (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
593 (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
594 path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
600 lastCentroid = inTouch->centroid;
602 inTouch->centroid.x += dx/inTouch->numDownFingers;
603 inTouch->centroid.y += dy/inTouch->numDownFingers;
604 /* printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */
605 if (inTouch->numDownFingers > 1) {
606 SDL_FloatPoint lv; /* Vector from centroid to last x,y position */
607 SDL_FloatPoint v; /* Vector from centroid to current x,y position */
608 /* lv = inTouch->gestureLast[j].cv; */
609 lv.x = lastP.x - lastCentroid.x;
610 lv.y = lastP.y - lastCentroid.y;
611 lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
612 /* printf("lDist = %f\n",lDist); */
613 v.x = x - inTouch->centroid.x;
614 v.y = y - inTouch->centroid.y;
615 /* inTouch->gestureLast[j].cv = v; */
616 Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
617 /* SDL_cos(dTheta) = (v . lv)/(|v| * |lv|) */
619 /* Normalize Vectors to simplify angle calculation */
624 dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
626 dDist = (Dist - lDist);
627 if (lDist == 0) {dDist = 0;dtheta = 0;} /* To avoid impossible values */
629 /* inTouch->gestureLast[j].dDist = dDist;
630 inTouch->gestureLast[j].dtheta = dtheta;
632 printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
633 gdtheta = gdtheta*.9 + dtheta*.1;
634 gdDist = gdDist*.9 + dDist*.1
635 knob.r += dDist/numDownFingers;
637 printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
638 printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */
639 SDL_SendGestureMulti(inTouch,dtheta,dDist);
642 /* inTouch->gestureLast[j].dDist = 0;
643 inTouch->gestureLast[j].dtheta = 0;
644 inTouch->gestureLast[j].cv.x = 0;
645 inTouch->gestureLast[j].cv.y = 0; */
647 /* inTouch->gestureLast[j].f.p.x = x;
648 inTouch->gestureLast[j].f.p.y = y;
652 else if (event->type == SDL_FINGERDOWN) {
654 inTouch->numDownFingers++;
655 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
656 x)/inTouch->numDownFingers;
657 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
658 y)/inTouch->numDownFingers;
659 /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
660 inTouch->centroid.x,inTouch->centroid.y); */
663 inTouch->dollarPath.length = 0;
664 inTouch->dollarPath.p[0].x = x;
665 inTouch->dollarPath.p[0].y = y;
666 inTouch->dollarPath.numPoints = 1;
672 /* vi: set ts=4 sw=4 expandtab: */