2 Simple DirectMedia Layer
3 Copyright (C) 1997-2013 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_config.h"
24 /* General mouse handling code for SDL */
26 #include "SDL_events.h"
27 #include "SDL_events_c.h"
28 #include "SDL_gesture_c.h"
35 //TODO: Replace with malloc
37 #define MAXPATHSIZE 1024
42 #define DOLLARNPOINTS 64
43 #define DOLLARSIZE 256
47 #define PHI 0.618033989
57 SDL_FloatPoint p[MAXPATHSIZE];
61 SDL_FloatPoint path[DOLLARNPOINTS];
68 SDL_FloatPoint centroid;
69 SDL_DollarPath dollarPath;
70 Uint16 numDownFingers;
72 int numDollarTemplates;
73 SDL_DollarTemplate *dollarTemplate;
78 SDL_GestureTouch *SDL_gestureTouch;
79 int SDL_numGestureTouches = 0;
83 static void PrintPath(SDL_FloatPoint *path)
87 for (i=0; i<DOLLARNPOINTS; i++) {
88 printf(" (%f,%f)",path[i].x,path[i].y);
94 int SDL_RecordGesture(SDL_TouchID touchId)
97 if (touchId < 0) recordAll = SDL_TRUE;
98 for (i = 0; i < SDL_numGestureTouches; i++) {
99 if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
100 SDL_gestureTouch[i].recording = SDL_TRUE;
105 return (touchId < 0);
108 static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
110 unsigned long hash = 5381;
112 for (i = 0; i < DOLLARNPOINTS; i++) {
113 hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
114 hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
120 static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops * src)
122 if (src == NULL) return 0;
125 //No Longer storing the Hash, rehash on load
126 //if(SDL_RWops.write(src,&(templ->hash),sizeof(templ->hash),1) != 1) return 0;
128 if (SDL_RWwrite(src,templ->path,
129 sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS)
136 int SDL_SaveAllDollarTemplates(SDL_RWops *src)
139 for (i = 0; i < SDL_numGestureTouches; i++) {
140 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
141 for (j = 0; j < touch->numDollarTemplates; j++) {
142 rtrn += SaveTemplate(&touch->dollarTemplate[i],src);
148 int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *src)
151 for (i = 0; i < SDL_numGestureTouches; i++) {
152 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
153 for (j = 0; j < touch->numDollarTemplates; j++) {
154 if (touch->dollarTemplate[i].hash == gestureId) {
155 return SaveTemplate(&touch->dollarTemplate[i],src);
159 SDL_SetError("Unknown gestureId");
163 //path is an already sampled set of points
164 //Returns the index of the gesture on success, or -1
165 static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
167 SDL_DollarTemplate* dollarTemplate;
168 SDL_DollarTemplate *templ;
171 index = inTouch->numDollarTemplates;
173 (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
175 sizeof(SDL_DollarTemplate));
176 if (!dollarTemplate) {
180 inTouch->dollarTemplate = dollarTemplate;
182 templ = &inTouch->dollarTemplate[index];
183 SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
184 templ->hash = SDL_HashDollar(templ->path);
185 inTouch->numDollarTemplates++;
190 static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
194 if (inTouch == NULL) {
195 if (SDL_numGestureTouches == 0) return -1;
196 for (i = 0; i < SDL_numGestureTouches; i++) {
197 inTouch = &SDL_gestureTouch[i];
198 index = SDL_AddDollarGesture_one(inTouch, path);
202 // Use the index of the last one added.
205 return SDL_AddDollarGesture_one(inTouch, path);
210 int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
213 SDL_GestureTouch *touch = NULL;
214 if (src == NULL) return 0;
216 for (i = 0; i < SDL_numGestureTouches; i++)
217 if (SDL_gestureTouch[i].id == touchId)
218 touch = &SDL_gestureTouch[i];
219 if (touch == NULL) return -1;
223 SDL_DollarTemplate templ;
225 if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) <
226 DOLLARNPOINTS) break;
229 //printf("Adding loaded gesture to 1 touch\n");
230 if (SDL_AddDollarGesture(touch, templ.path) >= 0)
234 //printf("Adding to: %i touches\n",SDL_numGestureTouches);
235 for (i = 0; i < SDL_numGestureTouches; i++) {
236 touch = &SDL_gestureTouch[i];
237 //printf("Adding loaded gesture to + touches\n");
238 //TODO: What if this fails?
239 SDL_AddDollarGesture(touch,templ.path);
249 static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
251 // SDL_FloatPoint p[DOLLARNPOINTS];
255 for (i = 0; i < DOLLARNPOINTS; i++) {
256 p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
257 p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
258 dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
259 (p.y-templ[i].y)*(p.y-templ[i].y)));
261 return dist/DOLLARNPOINTS;
265 static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
267 //------------BEGIN DOLLAR BLACKBOX----------------//
268 //-TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-//
269 //-"http://depts.washington.edu/aimgroup/proj/dollar/"-//
273 float x1 = (float)(PHI*ta + (1-PHI)*tb);
274 float f1 = dollarDifference(points,templ,x1);
275 float x2 = (float)((1-PHI)*ta + PHI*tb);
276 float f2 = dollarDifference(points,templ,x2);
277 while (SDL_fabs(ta-tb) > dt) {
282 x1 = (float)(PHI*ta + (1-PHI)*tb);
283 f1 = dollarDifference(points,templ,x1);
289 x2 = (float)((1-PHI)*ta + PHI*tb);
290 f2 = dollarDifference(points,templ,x2);
295 printf("Min angle (x1): %f\n",x1);
297 printf("Min angle (x2): %f\n",x2);
299 return SDL_min(f1,f2);
302 //DollarPath contains raw points, plus (possibly) the calculated length
303 static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points)
309 SDL_FloatPoint centroid;
310 float xmin,xmax,ymin,ymax;
313 float length = path->length;
315 //Calculate length if it hasn't already been done
317 for (i=1;i < path->numPoints; i++) {
318 float dx = path->p[i ].x - path->p[i-1].x;
319 float dy = path->p[i ].y - path->p[i-1].y;
320 length += (float)(SDL_sqrt(dx*dx+dy*dy));
325 interval = length/(DOLLARNPOINTS - 1);
328 centroid.x = 0;centroid.y = 0;
330 //printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y);
331 for (i = 1; i < path->numPoints; i++) {
332 float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
333 (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
334 //printf("d = %f dist = %f/%f\n",d,dist,interval);
335 while (dist + d > interval) {
336 points[numPoints].x = path->p[i-1].x +
337 ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
338 points[numPoints].y = path->p[i-1].y +
339 ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
340 centroid.x += points[numPoints].x;
341 centroid.y += points[numPoints].y;
348 if (numPoints < DOLLARNPOINTS-1) {
349 SDL_SetError("ERROR: NumPoints = %i\n",numPoints);
352 //copy the last point
353 points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
354 numPoints = DOLLARNPOINTS;
356 centroid.x /= numPoints;
357 centroid.y /= numPoints;
359 //printf("Centroid (%f,%f)",centroid.x,centroid.y);
360 //Rotate Points so point 0 is left of centroid and solve for the bounding box
366 ang = (float)(SDL_atan2(centroid.y - points[0].y,
367 centroid.x - points[0].x));
369 for (i = 0; i<numPoints; i++) {
370 float px = points[i].x;
371 float py = points[i].y;
372 points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
373 (py - centroid.y)*SDL_sin(ang) + centroid.x);
374 points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
375 (py - centroid.y)*SDL_cos(ang) + centroid.y);
378 if (points[i].x < xmin) xmin = points[i].x;
379 if (points[i].x > xmax) xmax = points[i].x;
380 if (points[i].y < ymin) ymin = points[i].y;
381 if (points[i].y > ymax) ymax = points[i].y;
384 //Scale points to DOLLARSIZE, and translate to the origin
388 for (i=0; i<numPoints; i++) {
389 points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
390 points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
395 static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
398 SDL_FloatPoint points[DOLLARNPOINTS];
400 float bestDiff = 10000;
402 dollarNormalize(path,points);
406 for (i = 0; i < touch->numDollarTemplates; i++) {
407 float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
408 if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
413 int SDL_GestureAddTouch(SDL_Touch* touch)
415 SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
416 (SDL_numGestureTouches + 1) *
417 sizeof(SDL_GestureTouch));
424 SDL_gestureTouch = gestureTouch;
426 SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
427 SDL_gestureTouch[SDL_numGestureTouches].res.y = touch->yres;
428 SDL_gestureTouch[SDL_numGestureTouches].numDownFingers = 0;
430 SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
431 SDL_gestureTouch[SDL_numGestureTouches].id = touch->id;
433 SDL_gestureTouch[SDL_numGestureTouches].numDollarTemplates = 0;
435 SDL_gestureTouch[SDL_numGestureTouches].recording = SDL_FALSE;
437 SDL_numGestureTouches++;
441 static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
444 for (i = 0; i < SDL_numGestureTouches; i++) {
445 //printf("%i ?= %i\n",SDL_gestureTouch[i].id,id);
446 if (SDL_gestureTouch[i].id == id)
447 return &SDL_gestureTouch[i];
452 int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
455 event.mgesture.type = SDL_MULTIGESTURE;
456 event.mgesture.touchId = touch->id;
457 event.mgesture.x = touch->centroid.x;
458 event.mgesture.y = touch->centroid.y;
459 event.mgesture.dTheta = dTheta;
460 event.mgesture.dDist = dDist;
461 event.mgesture.numFingers = touch->numDownFingers;
462 return SDL_PushEvent(&event) > 0;
465 static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
466 SDL_GestureID gestureId,float error)
469 event.dgesture.type = SDL_DOLLARGESTURE;
470 event.dgesture.touchId = touch->id;
472 //TODO: Add this to give location of gesture?
473 event.mgesture.x = touch->centroid.x;
474 event.mgesture.y = touch->centroid.y;
476 event.dgesture.gestureId = gestureId;
477 event.dgesture.error = error;
478 //A finger came up to trigger this event.
479 event.dgesture.numFingers = touch->numDownFingers + 1;
480 return SDL_PushEvent(&event) > 0;
484 static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
487 event.dgesture.type = SDL_DOLLARRECORD;
488 event.dgesture.touchId = touch->id;
489 event.dgesture.gestureId = gestureId;
490 return SDL_PushEvent(&event) > 0;
494 void SDL_GestureProcessEvent(SDL_Event* event)
497 SDL_FloatPoint path[DOLLARNPOINTS];
500 float pathDx, pathDy;
501 SDL_FloatPoint lastP;
502 SDL_FloatPoint lastCentroid;
508 if (event->type == SDL_FINGERMOTION ||
509 event->type == SDL_FINGERDOWN ||
510 event->type == SDL_FINGERUP) {
511 SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
513 //Shouldn't be possible
514 if (inTouch == NULL) return;
516 //printf("@ (%i,%i) with res: (%i,%i)\n",(int)event->tfinger.x,
517 // (int)event->tfinger.y,
518 // (int)inTouch->res.x,(int)inTouch->res.y);
521 x = ((float)event->tfinger.x)/(float)inTouch->res.x;
522 y = ((float)event->tfinger.y)/(float)inTouch->res.y;
526 if (event->type == SDL_FINGERUP) {
527 inTouch->numDownFingers--;
530 if (inTouch->recording) {
531 inTouch->recording = SDL_FALSE;
532 dollarNormalize(&inTouch->dollarPath,path);
535 index = SDL_AddDollarGesture(NULL,path);
536 for (i = 0; i < SDL_numGestureTouches; i++)
537 SDL_gestureTouch[i].recording = SDL_FALSE;
540 index = SDL_AddDollarGesture(inTouch,path);
544 SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
547 SDL_SendDollarRecord(inTouch,-1);
553 error = dollarRecognize(&inTouch->dollarPath,
557 unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
558 SDL_SendGestureDollar(inTouch,gestureId,error);
559 //printf ("%s\n",);("Dollar error: %f\n",error);
563 //inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers];
564 if (inTouch->numDownFingers > 0) {
565 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
566 x)/inTouch->numDownFingers;
567 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
568 y)/inTouch->numDownFingers;
571 else if (event->type == SDL_FINGERMOTION) {
572 float dx = ((float)event->tfinger.dx)/(float)inTouch->res.x;
573 float dy = ((float)event->tfinger.dy)/(float)inTouch->res.y;
574 //printf("dx,dy: (%f,%f)\n",dx,dy);
576 SDL_DollarPath* path = &inTouch->dollarPath;
577 if (path->numPoints < MAXPATHSIZE) {
578 path->p[path->numPoints].x = inTouch->centroid.x;
579 path->p[path->numPoints].y = inTouch->centroid.y;
581 (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
583 (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
584 path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
590 lastCentroid = inTouch->centroid;
592 inTouch->centroid.x += dx/inTouch->numDownFingers;
593 inTouch->centroid.y += dy/inTouch->numDownFingers;
594 //printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y);
595 if (inTouch->numDownFingers > 1) {
596 SDL_FloatPoint lv; //Vector from centroid to last x,y position
597 SDL_FloatPoint v; //Vector from centroid to current x,y position
598 //lv = inTouch->gestureLast[j].cv;
599 lv.x = lastP.x - lastCentroid.x;
600 lv.y = lastP.y - lastCentroid.y;
601 lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
602 //printf("lDist = %f\n",lDist);
603 v.x = x - inTouch->centroid.x;
604 v.y = y - inTouch->centroid.y;
605 //inTouch->gestureLast[j].cv = v;
606 Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
607 // SDL_cos(dTheta) = (v . lv)/(|v| * |lv|)
609 //Normalize Vectors to simplify angle calculation
614 dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
616 dDist = (Dist - lDist);
617 if (lDist == 0) {dDist = 0;dtheta = 0;} //To avoid impossible values
619 //inTouch->gestureLast[j].dDist = dDist;
620 //inTouch->gestureLast[j].dtheta = dtheta;
622 //printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
623 //gdtheta = gdtheta*.9 + dtheta*.1;
624 //gdDist = gdDist*.9 + dDist*.1
625 //knob.r += dDist/numDownFingers;
626 //knob.ang += dtheta;
627 //printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
628 //printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist);
629 SDL_SendGestureMulti(inTouch,dtheta,dDist);
632 //inTouch->gestureLast[j].dDist = 0;
633 //inTouch->gestureLast[j].dtheta = 0;
634 //inTouch->gestureLast[j].cv.x = 0;
635 //inTouch->gestureLast[j].cv.y = 0;
637 //inTouch->gestureLast[j].f.p.x = x;
638 //inTouch->gestureLast[j].f.p.y = y;
643 if (event->type == SDL_FINGERDOWN) {
645 inTouch->numDownFingers++;
646 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
647 x)/inTouch->numDownFingers;
648 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
649 y)/inTouch->numDownFingers;
650 //printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
651 // inTouch->centroid.x,inTouch->centroid.y);
654 inTouch->dollarPath.length = 0;
655 inTouch->dollarPath.p[0].x = x;
656 inTouch->dollarPath.p[0].y = y;
657 inTouch->dollarPath.numPoints = 1;
663 /* vi: set ts=4 sw=4 expandtab: */