2 Simple DirectMedia Layer
3 Copyright (C) 1997-2014 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_events_c.h"
28 #include "SDL_gesture_c.h"
38 /* TODO: Replace with malloc */
40 #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];
67 SDL_FloatPoint centroid;
68 SDL_DollarPath dollarPath;
69 Uint16 numDownFingers;
71 int numDollarTemplates;
72 SDL_DollarTemplate *dollarTemplate;
77 SDL_GestureTouch *SDL_gestureTouch;
78 int SDL_numGestureTouches = 0;
82 static void PrintPath(SDL_FloatPoint *path)
86 for (i=0; i<DOLLARNPOINTS; i++) {
87 printf(" (%f,%f)",path[i].x,path[i].y);
93 int SDL_RecordGesture(SDL_TouchID touchId)
96 if (touchId < 0) recordAll = SDL_TRUE;
97 for (i = 0; i < SDL_numGestureTouches; i++) {
98 if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
99 SDL_gestureTouch[i].recording = SDL_TRUE;
104 return (touchId < 0);
107 static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
109 unsigned long hash = 5381;
111 for (i = 0; i < DOLLARNPOINTS; i++) {
112 hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
113 hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
119 static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops *dst)
121 if (dst == NULL) return 0;
123 /* No Longer storing the Hash, rehash on load */
124 /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash), 1) != 1) return 0; */
126 if (SDL_RWwrite(dst, templ->path,
127 sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS)
134 int SDL_SaveAllDollarTemplates(SDL_RWops *dst)
137 for (i = 0; i < SDL_numGestureTouches; i++) {
138 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
139 for (j = 0; j < touch->numDollarTemplates; j++) {
140 rtrn += SaveTemplate(&touch->dollarTemplate[j], dst);
146 int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst)
149 for (i = 0; i < SDL_numGestureTouches; i++) {
150 SDL_GestureTouch* touch = &SDL_gestureTouch[i];
151 for (j = 0; j < touch->numDollarTemplates; j++) {
152 if (touch->dollarTemplate[j].hash == gestureId) {
153 return SaveTemplate(&touch->dollarTemplate[j], dst);
157 return SDL_SetError("Unknown gestureId");
160 /* path is an already sampled set of points
161 Returns the index of the gesture on success, or -1 */
162 static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
164 SDL_DollarTemplate* dollarTemplate;
165 SDL_DollarTemplate *templ;
168 index = inTouch->numDollarTemplates;
170 (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
172 sizeof(SDL_DollarTemplate));
173 if (!dollarTemplate) {
174 return SDL_OutOfMemory();
176 inTouch->dollarTemplate = dollarTemplate;
178 templ = &inTouch->dollarTemplate[index];
179 SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
180 templ->hash = SDL_HashDollar(templ->path);
181 inTouch->numDollarTemplates++;
186 static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
190 if (inTouch == NULL) {
191 if (SDL_numGestureTouches == 0) return -1;
192 for (i = 0; i < SDL_numGestureTouches; i++) {
193 inTouch = &SDL_gestureTouch[i];
194 index = SDL_AddDollarGesture_one(inTouch, path);
198 /* Use the index of the last one added. */
201 return SDL_AddDollarGesture_one(inTouch, path);
204 int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
207 SDL_GestureTouch *touch = NULL;
208 if (src == NULL) return 0;
210 for (i = 0; i < SDL_numGestureTouches; i++)
211 if (SDL_gestureTouch[i].id == touchId)
212 touch = &SDL_gestureTouch[i];
213 if (touch == NULL) return -1;
217 SDL_DollarTemplate templ;
219 if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) <
220 DOLLARNPOINTS) break;
223 /* printf("Adding loaded gesture to 1 touch\n"); */
224 if (SDL_AddDollarGesture(touch, templ.path) >= 0)
228 /* printf("Adding to: %i touches\n",SDL_numGestureTouches); */
229 for (i = 0; i < SDL_numGestureTouches; i++) {
230 touch = &SDL_gestureTouch[i];
231 /* printf("Adding loaded gesture to + touches\n"); */
232 /* TODO: What if this fails? */
233 SDL_AddDollarGesture(touch,templ.path);
243 static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
245 /* SDL_FloatPoint p[DOLLARNPOINTS]; */
249 for (i = 0; i < DOLLARNPOINTS; i++) {
250 p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
251 p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
252 dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
253 (p.y-templ[i].y)*(p.y-templ[i].y)));
255 return dist/DOLLARNPOINTS;
259 static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
261 /*------------BEGIN DOLLAR BLACKBOX------------------
262 -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-
263 -"http://depts.washington.edu/aimgroup/proj/dollar/"
268 float x1 = (float)(PHI*ta + (1-PHI)*tb);
269 float f1 = dollarDifference(points,templ,x1);
270 float x2 = (float)((1-PHI)*ta + PHI*tb);
271 float f2 = dollarDifference(points,templ,x2);
272 while (SDL_fabs(ta-tb) > dt) {
277 x1 = (float)(PHI*ta + (1-PHI)*tb);
278 f1 = dollarDifference(points,templ,x1);
284 x2 = (float)((1-PHI)*ta + PHI*tb);
285 f2 = dollarDifference(points,templ,x2);
290 printf("Min angle (x1): %f\n",x1);
292 printf("Min angle (x2): %f\n",x2);
294 return SDL_min(f1,f2);
297 /* DollarPath contains raw points, plus (possibly) the calculated length */
298 static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points)
304 SDL_FloatPoint centroid;
305 float xmin,xmax,ymin,ymax;
308 float length = path->length;
310 /* Calculate length if it hasn't already been done */
312 for (i=1;i < path->numPoints; i++) {
313 float dx = path->p[i ].x - path->p[i-1].x;
314 float dy = path->p[i ].y - path->p[i-1].y;
315 length += (float)(SDL_sqrt(dx*dx+dy*dy));
320 interval = length/(DOLLARNPOINTS - 1);
323 centroid.x = 0;centroid.y = 0;
325 /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); */
326 for (i = 1; i < path->numPoints; i++) {
327 float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
328 (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
329 /* printf("d = %f dist = %f/%f\n",d,dist,interval); */
330 while (dist + d > interval) {
331 points[numPoints].x = path->p[i-1].x +
332 ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
333 points[numPoints].y = path->p[i-1].y +
334 ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
335 centroid.x += points[numPoints].x;
336 centroid.y += points[numPoints].y;
343 if (numPoints < DOLLARNPOINTS-1) {
344 SDL_SetError("ERROR: NumPoints = %i\n",numPoints);
347 /* copy the last point */
348 points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
349 numPoints = DOLLARNPOINTS;
351 centroid.x /= numPoints;
352 centroid.y /= numPoints;
354 /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */
355 /* Rotate Points so point 0 is left of centroid and solve for the bounding box */
361 ang = (float)(SDL_atan2(centroid.y - points[0].y,
362 centroid.x - points[0].x));
364 for (i = 0; i<numPoints; i++) {
365 float px = points[i].x;
366 float py = points[i].y;
367 points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
368 (py - centroid.y)*SDL_sin(ang) + centroid.x);
369 points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
370 (py - centroid.y)*SDL_cos(ang) + centroid.y);
373 if (points[i].x < xmin) xmin = points[i].x;
374 if (points[i].x > xmax) xmax = points[i].x;
375 if (points[i].y < ymin) ymin = points[i].y;
376 if (points[i].y > ymax) ymax = points[i].y;
379 /* Scale points to DOLLARSIZE, and translate to the origin */
383 for (i=0; i<numPoints; i++) {
384 points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
385 points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
390 static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
392 SDL_FloatPoint points[DOLLARNPOINTS];
394 float bestDiff = 10000;
396 SDL_memset(points, 0, sizeof(points));
398 dollarNormalize(path,points);
400 /* PrintPath(points); */
402 for (i = 0; i < touch->numDollarTemplates; i++) {
403 float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
404 if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
409 int SDL_GestureAddTouch(SDL_TouchID touchId)
411 SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
412 (SDL_numGestureTouches + 1) *
413 sizeof(SDL_GestureTouch));
416 return SDL_OutOfMemory();
419 SDL_gestureTouch = gestureTouch;
421 SDL_zero(SDL_gestureTouch[SDL_numGestureTouches]);
422 SDL_gestureTouch[SDL_numGestureTouches].id = touchId;
423 SDL_numGestureTouches++;
427 static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
430 for (i = 0; i < SDL_numGestureTouches; i++) {
431 /* printf("%i ?= %i\n",SDL_gestureTouch[i].id,id); */
432 if (SDL_gestureTouch[i].id == id)
433 return &SDL_gestureTouch[i];
438 int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
441 event.mgesture.type = SDL_MULTIGESTURE;
442 event.mgesture.touchId = touch->id;
443 event.mgesture.x = touch->centroid.x;
444 event.mgesture.y = touch->centroid.y;
445 event.mgesture.dTheta = dTheta;
446 event.mgesture.dDist = dDist;
447 event.mgesture.numFingers = touch->numDownFingers;
448 return SDL_PushEvent(&event) > 0;
451 static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
452 SDL_GestureID gestureId,float error)
455 event.dgesture.type = SDL_DOLLARGESTURE;
456 event.dgesture.touchId = touch->id;
457 event.dgesture.x = touch->centroid.x;
458 event.dgesture.y = touch->centroid.y;
459 event.dgesture.gestureId = gestureId;
460 event.dgesture.error = error;
461 /* A finger came up to trigger this event. */
462 event.dgesture.numFingers = touch->numDownFingers + 1;
463 return SDL_PushEvent(&event) > 0;
467 static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
470 event.dgesture.type = SDL_DOLLARRECORD;
471 event.dgesture.touchId = touch->id;
472 event.dgesture.gestureId = gestureId;
473 return SDL_PushEvent(&event) > 0;
477 void SDL_GestureProcessEvent(SDL_Event* event)
480 SDL_FloatPoint path[DOLLARNPOINTS];
483 float pathDx, pathDy;
484 SDL_FloatPoint lastP;
485 SDL_FloatPoint lastCentroid;
491 if (event->type == SDL_FINGERMOTION ||
492 event->type == SDL_FINGERDOWN ||
493 event->type == SDL_FINGERUP) {
494 SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
496 /* Shouldn't be possible */
497 if (inTouch == NULL) return;
499 x = event->tfinger.x;
500 y = event->tfinger.y;
503 if (event->type == SDL_FINGERUP) {
504 inTouch->numDownFingers--;
507 if (inTouch->recording) {
508 inTouch->recording = SDL_FALSE;
509 dollarNormalize(&inTouch->dollarPath,path);
510 /* PrintPath(path); */
512 index = SDL_AddDollarGesture(NULL,path);
513 for (i = 0; i < SDL_numGestureTouches; i++)
514 SDL_gestureTouch[i].recording = SDL_FALSE;
517 index = SDL_AddDollarGesture(inTouch,path);
521 SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
524 SDL_SendDollarRecord(inTouch,-1);
530 error = dollarRecognize(&inTouch->dollarPath,
534 unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
535 SDL_SendGestureDollar(inTouch,gestureId,error);
536 /* printf ("%s\n",);("Dollar error: %f\n",error); */
540 /* inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; */
541 if (inTouch->numDownFingers > 0) {
542 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
543 x)/inTouch->numDownFingers;
544 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
545 y)/inTouch->numDownFingers;
548 else if (event->type == SDL_FINGERMOTION) {
549 float dx = event->tfinger.dx;
550 float dy = event->tfinger.dy;
552 SDL_DollarPath* path = &inTouch->dollarPath;
553 if (path->numPoints < MAXPATHSIZE) {
554 path->p[path->numPoints].x = inTouch->centroid.x;
555 path->p[path->numPoints].y = inTouch->centroid.y;
557 (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
559 (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
560 path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
566 lastCentroid = inTouch->centroid;
568 inTouch->centroid.x += dx/inTouch->numDownFingers;
569 inTouch->centroid.y += dy/inTouch->numDownFingers;
570 /* printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */
571 if (inTouch->numDownFingers > 1) {
572 SDL_FloatPoint lv; /* Vector from centroid to last x,y position */
573 SDL_FloatPoint v; /* Vector from centroid to current x,y position */
574 /* lv = inTouch->gestureLast[j].cv; */
575 lv.x = lastP.x - lastCentroid.x;
576 lv.y = lastP.y - lastCentroid.y;
577 lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
578 /* printf("lDist = %f\n",lDist); */
579 v.x = x - inTouch->centroid.x;
580 v.y = y - inTouch->centroid.y;
581 /* inTouch->gestureLast[j].cv = v; */
582 Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
583 /* SDL_cos(dTheta) = (v . lv)/(|v| * |lv|) */
585 /* Normalize Vectors to simplify angle calculation */
590 dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
592 dDist = (Dist - lDist);
593 if (lDist == 0) {dDist = 0;dtheta = 0;} /* To avoid impossible values */
595 /* inTouch->gestureLast[j].dDist = dDist;
596 inTouch->gestureLast[j].dtheta = dtheta;
598 printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
599 gdtheta = gdtheta*.9 + dtheta*.1;
600 gdDist = gdDist*.9 + dDist*.1
601 knob.r += dDist/numDownFingers;
603 printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
604 printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */
605 SDL_SendGestureMulti(inTouch,dtheta,dDist);
608 /* inTouch->gestureLast[j].dDist = 0;
609 inTouch->gestureLast[j].dtheta = 0;
610 inTouch->gestureLast[j].cv.x = 0;
611 inTouch->gestureLast[j].cv.y = 0; */
613 /* inTouch->gestureLast[j].f.p.x = x;
614 inTouch->gestureLast[j].f.p.y = y;
619 if (event->type == SDL_FINGERDOWN) {
621 inTouch->numDownFingers++;
622 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
623 x)/inTouch->numDownFingers;
624 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
625 y)/inTouch->numDownFingers;
626 /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
627 inTouch->centroid.x,inTouch->centroid.y); */
630 inTouch->dollarPath.length = 0;
631 inTouch->dollarPath.p[0].x = x;
632 inTouch->dollarPath.p[0].y = y;
633 inTouch->dollarPath.numPoints = 1;
639 /* vi: set ts=4 sw=4 expandtab: */