SDL_gesture.c (20584B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> 4 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. 8 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: 12 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. 20*/ 21 22#include "../SDL_internal.h" 23 24/* General mouse handling code for SDL */ 25 26#include "SDL_events.h" 27#include "SDL_events_c.h" 28#include "SDL_gesture_c.h" 29 30/* 31#include <stdio.h> 32*/ 33 34/* TODO: Replace with malloc */ 35 36#define MAXPATHSIZE 1024 37 38#define DOLLARNPOINTS 64 39#define DOLLARSIZE 256 40 41#define ENABLE_DOLLAR 42 43#define PHI 0.618033989 44 45typedef struct { 46 float x,y; 47} SDL_FloatPoint; 48 49typedef struct { 50 float length; 51 52 int numPoints; 53 SDL_FloatPoint p[MAXPATHSIZE]; 54} SDL_DollarPath; 55 56typedef struct { 57 SDL_FloatPoint path[DOLLARNPOINTS]; 58 unsigned long hash; 59} SDL_DollarTemplate; 60 61typedef struct { 62 SDL_TouchID id; 63 SDL_FloatPoint centroid; 64 SDL_DollarPath dollarPath; 65 Uint16 numDownFingers; 66 67 int numDollarTemplates; 68 SDL_DollarTemplate *dollarTemplate; 69 70 SDL_bool recording; 71} SDL_GestureTouch; 72 73SDL_GestureTouch *SDL_gestureTouch; 74int SDL_numGestureTouches = 0; 75SDL_bool recordAll; 76 77#if 0 78static void PrintPath(SDL_FloatPoint *path) 79{ 80 int i; 81 printf("Path:"); 82 for (i=0; i<DOLLARNPOINTS; i++) { 83 printf(" (%f,%f)",path[i].x,path[i].y); 84 } 85 printf("\n"); 86} 87#endif 88 89int SDL_RecordGesture(SDL_TouchID touchId) 90{ 91 int i; 92 if (touchId < 0) recordAll = SDL_TRUE; 93 for (i = 0; i < SDL_numGestureTouches; i++) { 94 if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) { 95 SDL_gestureTouch[i].recording = SDL_TRUE; 96 if (touchId >= 0) 97 return 1; 98 } 99 } 100 return (touchId < 0); 101} 102 103static unsigned long SDL_HashDollar(SDL_FloatPoint* points) 104{ 105 unsigned long hash = 5381; 106 int i; 107 for (i = 0; i < DOLLARNPOINTS; i++) { 108 hash = ((hash<<5) + hash) + (unsigned long)points[i].x; 109 hash = ((hash<<5) + hash) + (unsigned long)points[i].y; 110 } 111 return hash; 112} 113 114 115static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops *dst) 116{ 117 if (dst == NULL) return 0; 118 119 /* No Longer storing the Hash, rehash on load */ 120 /* if (SDL_RWops.write(dst, &(templ->hash), sizeof(templ->hash), 1) != 1) return 0; */ 121 122 if (SDL_RWwrite(dst, templ->path, 123 sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS) 124 return 0; 125 126 return 1; 127} 128 129 130int SDL_SaveAllDollarTemplates(SDL_RWops *dst) 131{ 132 int i,j,rtrn = 0; 133 for (i = 0; i < SDL_numGestureTouches; i++) { 134 SDL_GestureTouch* touch = &SDL_gestureTouch[i]; 135 for (j = 0; j < touch->numDollarTemplates; j++) { 136 rtrn += SaveTemplate(&touch->dollarTemplate[j], dst); 137 } 138 } 139 return rtrn; 140} 141 142int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst) 143{ 144 int i,j; 145 for (i = 0; i < SDL_numGestureTouches; i++) { 146 SDL_GestureTouch* touch = &SDL_gestureTouch[i]; 147 for (j = 0; j < touch->numDollarTemplates; j++) { 148 if (touch->dollarTemplate[j].hash == gestureId) { 149 return SaveTemplate(&touch->dollarTemplate[j], dst); 150 } 151 } 152 } 153 return SDL_SetError("Unknown gestureId"); 154} 155 156/* path is an already sampled set of points 157Returns the index of the gesture on success, or -1 */ 158static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path) 159{ 160 SDL_DollarTemplate* dollarTemplate; 161 SDL_DollarTemplate *templ; 162 int index; 163 164 index = inTouch->numDollarTemplates; 165 dollarTemplate = 166 (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate, 167 (index + 1) * 168 sizeof(SDL_DollarTemplate)); 169 if (!dollarTemplate) { 170 return SDL_OutOfMemory(); 171 } 172 inTouch->dollarTemplate = dollarTemplate; 173 174 templ = &inTouch->dollarTemplate[index]; 175 SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint)); 176 templ->hash = SDL_HashDollar(templ->path); 177 inTouch->numDollarTemplates++; 178 179 return index; 180} 181 182static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path) 183{ 184 int index = -1; 185 int i = 0; 186 if (inTouch == NULL) { 187 if (SDL_numGestureTouches == 0) return -1; 188 for (i = 0; i < SDL_numGestureTouches; i++) { 189 inTouch = &SDL_gestureTouch[i]; 190 index = SDL_AddDollarGesture_one(inTouch, path); 191 if (index < 0) 192 return -1; 193 } 194 /* Use the index of the last one added. */ 195 return index; 196 } 197 return SDL_AddDollarGesture_one(inTouch, path); 198} 199 200int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src) 201{ 202 int i,loaded = 0; 203 SDL_GestureTouch *touch = NULL; 204 if (src == NULL) return 0; 205 if (touchId >= 0) { 206 for (i = 0; i < SDL_numGestureTouches; i++) 207 if (SDL_gestureTouch[i].id == touchId) 208 touch = &SDL_gestureTouch[i]; 209 if (touch == NULL) return -1; 210 } 211 212 while (1) { 213 SDL_DollarTemplate templ; 214 215 if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) < 216 DOLLARNPOINTS) break; 217 218 if (touchId >= 0) { 219 /* printf("Adding loaded gesture to 1 touch\n"); */ 220 if (SDL_AddDollarGesture(touch, templ.path) >= 0) 221 loaded++; 222 } 223 else { 224 /* printf("Adding to: %i touches\n",SDL_numGestureTouches); */ 225 for (i = 0; i < SDL_numGestureTouches; i++) { 226 touch = &SDL_gestureTouch[i]; 227 /* printf("Adding loaded gesture to + touches\n"); */ 228 /* TODO: What if this fails? */ 229 SDL_AddDollarGesture(touch,templ.path); 230 } 231 loaded++; 232 } 233 } 234 235 return loaded; 236} 237 238 239static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang) 240{ 241 /* SDL_FloatPoint p[DOLLARNPOINTS]; */ 242 float dist = 0; 243 SDL_FloatPoint p; 244 int i; 245 for (i = 0; i < DOLLARNPOINTS; i++) { 246 p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang)); 247 p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang)); 248 dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+ 249 (p.y-templ[i].y)*(p.y-templ[i].y))); 250 } 251 return dist/DOLLARNPOINTS; 252 253} 254 255static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ) 256{ 257 /*------------BEGIN DOLLAR BLACKBOX------------------ 258 -TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT- 259 -"http://depts.washington.edu/aimgroup/proj/dollar/" 260 */ 261 double ta = -M_PI/4; 262 double tb = M_PI/4; 263 double dt = M_PI/90; 264 float x1 = (float)(PHI*ta + (1-PHI)*tb); 265 float f1 = dollarDifference(points,templ,x1); 266 float x2 = (float)((1-PHI)*ta + PHI*tb); 267 float f2 = dollarDifference(points,templ,x2); 268 while (SDL_fabs(ta-tb) > dt) { 269 if (f1 < f2) { 270 tb = x2; 271 x2 = x1; 272 f2 = f1; 273 x1 = (float)(PHI*ta + (1-PHI)*tb); 274 f1 = dollarDifference(points,templ,x1); 275 } 276 else { 277 ta = x1; 278 x1 = x2; 279 f1 = f2; 280 x2 = (float)((1-PHI)*ta + PHI*tb); 281 f2 = dollarDifference(points,templ,x2); 282 } 283 } 284 /* 285 if (f1 <= f2) 286 printf("Min angle (x1): %f\n",x1); 287 else if (f1 > f2) 288 printf("Min angle (x2): %f\n",x2); 289 */ 290 return SDL_min(f1,f2); 291} 292 293/* DollarPath contains raw points, plus (possibly) the calculated length */ 294static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points) 295{ 296 int i; 297 float interval; 298 float dist; 299 int numPoints = 0; 300 SDL_FloatPoint centroid; 301 float xmin,xmax,ymin,ymax; 302 float ang; 303 float w,h; 304 float length = path->length; 305 306 /* Calculate length if it hasn't already been done */ 307 if (length <= 0) { 308 for (i=1;i < path->numPoints; i++) { 309 float dx = path->p[i ].x - path->p[i-1].x; 310 float dy = path->p[i ].y - path->p[i-1].y; 311 length += (float)(SDL_sqrt(dx*dx+dy*dy)); 312 } 313 } 314 315 /* Resample */ 316 interval = length/(DOLLARNPOINTS - 1); 317 dist = interval; 318 319 centroid.x = 0;centroid.y = 0; 320 321 /* printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y); */ 322 for (i = 1; i < path->numPoints; i++) { 323 float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+ 324 (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y))); 325 /* printf("d = %f dist = %f/%f\n",d,dist,interval); */ 326 while (dist + d > interval) { 327 points[numPoints].x = path->p[i-1].x + 328 ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x); 329 points[numPoints].y = path->p[i-1].y + 330 ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y); 331 centroid.x += points[numPoints].x; 332 centroid.y += points[numPoints].y; 333 numPoints++; 334 335 dist -= interval; 336 } 337 dist += d; 338 } 339 if (numPoints < DOLLARNPOINTS-1) { 340 SDL_SetError("ERROR: NumPoints = %i\n",numPoints); 341 return 0; 342 } 343 /* copy the last point */ 344 points[DOLLARNPOINTS-1] = path->p[path->numPoints-1]; 345 numPoints = DOLLARNPOINTS; 346 347 centroid.x /= numPoints; 348 centroid.y /= numPoints; 349 350 /* printf("Centroid (%f,%f)",centroid.x,centroid.y); */ 351 /* Rotate Points so point 0 is left of centroid and solve for the bounding box */ 352 xmin = centroid.x; 353 xmax = centroid.x; 354 ymin = centroid.y; 355 ymax = centroid.y; 356 357 ang = (float)(SDL_atan2(centroid.y - points[0].y, 358 centroid.x - points[0].x)); 359 360 for (i = 0; i<numPoints; i++) { 361 float px = points[i].x; 362 float py = points[i].y; 363 points[i].x = (float)((px - centroid.x)*SDL_cos(ang) - 364 (py - centroid.y)*SDL_sin(ang) + centroid.x); 365 points[i].y = (float)((px - centroid.x)*SDL_sin(ang) + 366 (py - centroid.y)*SDL_cos(ang) + centroid.y); 367 368 369 if (points[i].x < xmin) xmin = points[i].x; 370 if (points[i].x > xmax) xmax = points[i].x; 371 if (points[i].y < ymin) ymin = points[i].y; 372 if (points[i].y > ymax) ymax = points[i].y; 373 } 374 375 /* Scale points to DOLLARSIZE, and translate to the origin */ 376 w = xmax-xmin; 377 h = ymax-ymin; 378 379 for (i=0; i<numPoints; i++) { 380 points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w; 381 points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h; 382 } 383 return numPoints; 384} 385 386static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch) 387{ 388 SDL_FloatPoint points[DOLLARNPOINTS]; 389 int i; 390 float bestDiff = 10000; 391 392 SDL_memset(points, 0, sizeof(points)); 393 394 dollarNormalize(path,points); 395 396 /* PrintPath(points); */ 397 *bestTempl = -1; 398 for (i = 0; i < touch->numDollarTemplates; i++) { 399 float diff = bestDollarDifference(points,touch->dollarTemplate[i].path); 400 if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;} 401 } 402 return bestDiff; 403} 404 405int SDL_GestureAddTouch(SDL_TouchID touchId) 406{ 407 SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch, 408 (SDL_numGestureTouches + 1) * 409 sizeof(SDL_GestureTouch)); 410 411 if (!gestureTouch) { 412 return SDL_OutOfMemory(); 413 } 414 415 SDL_gestureTouch = gestureTouch; 416 417 SDL_zero(SDL_gestureTouch[SDL_numGestureTouches]); 418 SDL_gestureTouch[SDL_numGestureTouches].id = touchId; 419 SDL_numGestureTouches++; 420 return 0; 421} 422 423static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id) 424{ 425 int i; 426 for (i = 0; i < SDL_numGestureTouches; i++) { 427 /* printf("%i ?= %i\n",SDL_gestureTouch[i].id,id); */ 428 if (SDL_gestureTouch[i].id == id) 429 return &SDL_gestureTouch[i]; 430 } 431 return NULL; 432} 433 434int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist) 435{ 436 SDL_Event event; 437 event.mgesture.type = SDL_MULTIGESTURE; 438 event.mgesture.touchId = touch->id; 439 event.mgesture.x = touch->centroid.x; 440 event.mgesture.y = touch->centroid.y; 441 event.mgesture.dTheta = dTheta; 442 event.mgesture.dDist = dDist; 443 event.mgesture.numFingers = touch->numDownFingers; 444 return SDL_PushEvent(&event) > 0; 445} 446 447static int SDL_SendGestureDollar(SDL_GestureTouch* touch, 448 SDL_GestureID gestureId,float error) 449{ 450 SDL_Event event; 451 event.dgesture.type = SDL_DOLLARGESTURE; 452 event.dgesture.touchId = touch->id; 453 event.dgesture.x = touch->centroid.x; 454 event.dgesture.y = touch->centroid.y; 455 event.dgesture.gestureId = gestureId; 456 event.dgesture.error = error; 457 /* A finger came up to trigger this event. */ 458 event.dgesture.numFingers = touch->numDownFingers + 1; 459 return SDL_PushEvent(&event) > 0; 460} 461 462 463static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId) 464{ 465 SDL_Event event; 466 event.dgesture.type = SDL_DOLLARRECORD; 467 event.dgesture.touchId = touch->id; 468 event.dgesture.gestureId = gestureId; 469 return SDL_PushEvent(&event) > 0; 470} 471 472 473void SDL_GestureProcessEvent(SDL_Event* event) 474{ 475 float x,y; 476 int index; 477 int i; 478 float pathDx, pathDy; 479 SDL_FloatPoint lastP; 480 SDL_FloatPoint lastCentroid; 481 float lDist; 482 float Dist; 483 float dtheta; 484 float dDist; 485 486 if (event->type == SDL_FINGERMOTION || 487 event->type == SDL_FINGERDOWN || 488 event->type == SDL_FINGERUP) { 489 SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId); 490 491 /* Shouldn't be possible */ 492 if (inTouch == NULL) return; 493 494 x = event->tfinger.x; 495 y = event->tfinger.y; 496 497 /* Finger Up */ 498 if (event->type == SDL_FINGERUP) { 499 SDL_FloatPoint path[DOLLARNPOINTS]; 500 501 inTouch->numDownFingers--; 502 503#ifdef ENABLE_DOLLAR 504 if (inTouch->recording) { 505 inTouch->recording = SDL_FALSE; 506 dollarNormalize(&inTouch->dollarPath,path); 507 /* PrintPath(path); */ 508 if (recordAll) { 509 index = SDL_AddDollarGesture(NULL,path); 510 for (i = 0; i < SDL_numGestureTouches; i++) 511 SDL_gestureTouch[i].recording = SDL_FALSE; 512 } 513 else { 514 index = SDL_AddDollarGesture(inTouch,path); 515 } 516 517 if (index >= 0) { 518 SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash); 519 } 520 else { 521 SDL_SendDollarRecord(inTouch,-1); 522 } 523 } 524 else { 525 int bestTempl; 526 float error; 527 error = dollarRecognize(&inTouch->dollarPath, 528 &bestTempl,inTouch); 529 if (bestTempl >= 0){ 530 /* Send Event */ 531 unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash; 532 SDL_SendGestureDollar(inTouch,gestureId,error); 533 /* printf ("%s\n",);("Dollar error: %f\n",error); */ 534 } 535 } 536#endif 537 /* inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; */ 538 if (inTouch->numDownFingers > 0) { 539 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)- 540 x)/inTouch->numDownFingers; 541 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)- 542 y)/inTouch->numDownFingers; 543 } 544 } 545 else if (event->type == SDL_FINGERMOTION) { 546 float dx = event->tfinger.dx; 547 float dy = event->tfinger.dy; 548#ifdef ENABLE_DOLLAR 549 SDL_DollarPath* path = &inTouch->dollarPath; 550 if (path->numPoints < MAXPATHSIZE) { 551 path->p[path->numPoints].x = inTouch->centroid.x; 552 path->p[path->numPoints].y = inTouch->centroid.y; 553 pathDx = 554 (path->p[path->numPoints].x-path->p[path->numPoints-1].x); 555 pathDy = 556 (path->p[path->numPoints].y-path->p[path->numPoints-1].y); 557 path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy); 558 path->numPoints++; 559 } 560#endif 561 lastP.x = x - dx; 562 lastP.y = y - dy; 563 lastCentroid = inTouch->centroid; 564 565 inTouch->centroid.x += dx/inTouch->numDownFingers; 566 inTouch->centroid.y += dy/inTouch->numDownFingers; 567 /* printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y); */ 568 if (inTouch->numDownFingers > 1) { 569 SDL_FloatPoint lv; /* Vector from centroid to last x,y position */ 570 SDL_FloatPoint v; /* Vector from centroid to current x,y position */ 571 /* lv = inTouch->gestureLast[j].cv; */ 572 lv.x = lastP.x - lastCentroid.x; 573 lv.y = lastP.y - lastCentroid.y; 574 lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y); 575 /* printf("lDist = %f\n",lDist); */ 576 v.x = x - inTouch->centroid.x; 577 v.y = y - inTouch->centroid.y; 578 /* inTouch->gestureLast[j].cv = v; */ 579 Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y); 580 /* SDL_cos(dTheta) = (v . lv)/(|v| * |lv|) */ 581 582 /* Normalize Vectors to simplify angle calculation */ 583 lv.x/=lDist; 584 lv.y/=lDist; 585 v.x/=Dist; 586 v.y/=Dist; 587 dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y); 588 589 dDist = (Dist - lDist); 590 if (lDist == 0) {dDist = 0;dtheta = 0;} /* To avoid impossible values */ 591 592 /* inTouch->gestureLast[j].dDist = dDist; 593 inTouch->gestureLast[j].dtheta = dtheta; 594 595 printf("dDist = %f, dTheta = %f\n",dDist,dtheta); 596 gdtheta = gdtheta*.9 + dtheta*.1; 597 gdDist = gdDist*.9 + dDist*.1 598 knob.r += dDist/numDownFingers; 599 knob.ang += dtheta; 600 printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist); 601 printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); */ 602 SDL_SendGestureMulti(inTouch,dtheta,dDist); 603 } 604 else { 605 /* inTouch->gestureLast[j].dDist = 0; 606 inTouch->gestureLast[j].dtheta = 0; 607 inTouch->gestureLast[j].cv.x = 0; 608 inTouch->gestureLast[j].cv.y = 0; */ 609 } 610 /* inTouch->gestureLast[j].f.p.x = x; 611 inTouch->gestureLast[j].f.p.y = y; 612 break; 613 pressure? */ 614 } 615 616 if (event->type == SDL_FINGERDOWN) { 617 618 inTouch->numDownFingers++; 619 inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+ 620 x)/inTouch->numDownFingers; 621 inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+ 622 y)/inTouch->numDownFingers; 623 /* printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y, 624 inTouch->centroid.x,inTouch->centroid.y); */ 625 626#ifdef ENABLE_DOLLAR 627 inTouch->dollarPath.length = 0; 628 inTouch->dollarPath.p[0].x = x; 629 inTouch->dollarPath.p[0].y = y; 630 inTouch->dollarPath.numPoints = 1; 631#endif 632 } 633 } 634} 635 636/* vi: set ts=4 sw=4 expandtab: */