From ec45de353001c71e23a7a28ef7e48145e709a41f Mon Sep 17 00:00:00 2001 From: Jim Grandpre Date: Sun, 15 Aug 2010 00:36:28 -0400 Subject: [PATCH] Added README.touch and README.gesture. Moved touchtest/gestureSDLTest to test/testgesture --- README.gesture | 73 +++++++++ README.iphoneos | 8 - README.touch | 77 ++++++++++ include/SDL_events.h | 1 + include/SDL_touch.h | 2 +- src/events/SDL_gesture.c | 3 +- src/events/SDL_touch.c | 4 +- test/Makefile.in | 5 +- test/testgesture.c | 297 +++++++++++++++++++++++++++++++++++++ touchTest/gestureSDLTest.c | 7 + 10 files changed, 464 insertions(+), 13 deletions(-) create mode 100644 README.gesture create mode 100644 README.touch create mode 100644 test/testgesture.c diff --git a/README.gesture b/README.gesture new file mode 100644 index 000000000..6d4deea55 --- /dev/null +++ b/README.gesture @@ -0,0 +1,73 @@ +=========================================================================== +Dollar Gestures +=========================================================================== +SDL Provides an implementation of the $1 gesture recognition system. This allows for recording, saving, loading, and performing single stroke gestures. + +Gestures can be performed with any number of fingers (the centroid of the fingers must follow the path of the gesture), but the number of fingers must be constant (a finger cannot go down in the middle of a gesture). The path of a gesture is considered the path from the time when the final finger went down, to the first time any finger comes up. + +Dollar gestures are assigned an Id based on a hash function. This is guaranteed to remain constant for a given gesture. There is a (small) chance that two different gestures will be assigned the same ID. In this simply re-recording on of the gestures should result in a different ID. + +Recording: +---------- +To begin recording on a touch device call: +SDL_RecordGesture(SDL_TouchID touchId), where touchId is the id of the touch device you wish to record on, or -1 to record on all connected devices. + +Recording terminates as soon as a finger comes up. Recording is acknowledged by an SDL_DOLLARRECORD event. +A SDL_DOLLARRECORD event is a dgesture with the following fields: + +event.dgesture.touchId - the Id of the touch used to record the gesture. +event.dgesture.gestureId - the unique id of the recoreded gesture. + + + +Performing: +----------- +As long as there is a dollar gesture assigned to a touch, every finger-up event will also cause an SDL_DOLLARGESTURE event with the following fields: + +event.dgesture.touchId - the Id of the touch which performed the gesture. +event.dgesture.gestureId - the unique id of the closest gesture to the performed stroke. +event.dgesture.error - the difference between the gesture template and the actual performed gesture. Lower error is a better match. +event.dgesture.numFingers - the number of fingers used to draw the stroke. + +Most programs will want to define an appropriate error threshold and check to be sure taht the error of a gesture is not abnormally high (an indicator that no gesture was performed). + + + +Saving: +------- +To save a template, call SDL_SaveDollarTemplate(gestureId, src) where gestureId is the id of the gesture you want to save, and src is an SDL_RWops pointer to the file where the gesture will be stored. + +To save all currently loaded templatesm, call SDL_SaveAllDollarTemplates(src) where source is an SDL_RWops pointer to the file where the gesture will be stored. + +Both functions return the number of gestures sucessfully saved. + + +Loading: +-------- +To load templates from a file, call SDL_LoadDollarTemplates(touchId,src) where touchId is the id of the touch to load to (or -1 to load to all touch devices), and src is an SDL_RWops pointer to a gesture save file. + +SDL_LoadDollarTemplates returns the number of templates sucessfully loaded. + + + +=========================================================================== +Multi Gestures +=========================================================================== +SDL provides simple support to pinch/rotate/swipe gestures. +Every time a finger is moved an SDL_MULTIGESTURE event is sent with the following fields: + +event.mgesture.touchId - the Id of the touch on which the gesture was performed. +event.mgesture.x - the normalized x cooridinate of the gesture. (0..1) +event.mgesture.y - the normalized y cooridinate of the gesture. (0..1) +event.mgesture.dTheta - the amount that the fingers rotated during this motion. +event.mgesture.dDist - the amount that the fingers pinched during this motion. +event.mgesture.numFingers - the number of fingers used in the gesture. + + +=========================================================================== +Notes +=========================================================================== +For a complete example see test/testgesture.c + +Please direct questions/comments to: + jim.tla+sdl_touch@gmail.com \ No newline at end of file diff --git a/README.iphoneos b/README.iphoneos index c68aabf6e..39ff14da9 100644 --- a/README.iphoneos +++ b/README.iphoneos @@ -33,14 +33,6 @@ Here is a more manual method: 4. Remove the ApplicationDelegate.h and ApplicationDelegate.m files -- SDL for iPhone provides its own UIApplicationDelegate. Remove MainWindow.xib -- SDL for iPhone produces its user interface programmatically. 5. Delete the contents of main.m and program your app as a regular SDL program instead. You may replace main.m with your own main.c, but you must tell XCode not to use the project prefix file, as it includes Objective-C code. -============================================================================== -Notes -- Touch Input -============================================================================== - -Touch input in SDL for iPhone OS is presently exposed through SDL's mouse input API. Multi-touch input is reported as multiple mice, with each touch associated with a specific mouse. This association stays coherent from the time the touch starts to the time a touch ends. - -By default, multi-touch is turned ON. This requires some care, because if you simply respond to mouse events without checking which mouse caused the event, you may end up fetching data from the wrong mouse, ie, from an incorrect or invalid touch. To turn multi-touch OFF, you can recompile SDL for iPhone with the macro SDL_IPHONE_MULTIPLE_MICE (found in SDL_config_iphoneos.h) set to 0. - ============================================================================== Notes -- Accelerometer as Joystick ============================================================================== diff --git a/README.touch b/README.touch new file mode 100644 index 000000000..becb89fbd --- /dev/null +++ b/README.touch @@ -0,0 +1,77 @@ +=========================================================================== +Events +=========================================================================== +SDL_FINGERDOWN: +Sent when a finger (or stylus) is placed on a touch device. +Fields: +event.tfinger.touchId - the Id of the touch device. +event.tfinger.fingerId - the Id of the finger which just went down. +event.tfinger.x - the x coordinate of the touch (0..touch.xres) +event.tfinger.y - the y coordinate of the touch (0..touch.yres) +event.tfinger.pressure - the pressure of the touch (0..touch.pressureres) + +SDL_FINGERMOTION: +Sent when a finger (or stylus) is moved on the touch device. +Fields: +Same as FINGERDOWN but with additional: +event.tfginer.dx - chagne in x coordinate during this motion event. +event.tfginer.dy - chagne in y coordinate during this motion event. + +SDL_FINGERMOTION: +Sent when a finger (or stylus) is lifted from the touch device. +Fields: +Same as FINGERDOWN. + + +=========================================================================== +Functions +=========================================================================== +SDL provides the ability to access the underlying Touch and Finger structures. +These structures should _never_ be modified. + +The following functions are included from SDL_Touch.h + +To get a SDL_Touch device call SDL_GetTouch(touchId). +This returns an SDL_Touch*. +IMPORTANT: If the touch has been removed, or there is no touch with the given ID, SDL_GetTouch will return null. Be sure to check for this! + +An SDL_Touch has the following fields: +>pressure_max, pressure_min, x_max, x_min, y_max, y_min + Which give, respectively, the maximum and minumum values that the touch digitizer can return for pressure, x coordiniate, and y coordinate AS REPORTED BY THE OPERATING SYSTEM. +On Mac/iPhone systems _max will always be 0, and _min will always be 1. + +>xres,yres,pressures: + The resolution at which x,y, and pressure values are reported. Currently these will always be equal to 2^15, but this may not always be the case. + +>native_xres,native_yres,native_pressureres: + The native resolution of the touch device AS REPORTED BY THE OPERATING SYSTEM. +On Mac/iPhone systems these will always be 1. + +>num_fingers: + The number of fingers currently down on the device. + +>fingers: + An array of pointers to the fingers which are on the device. + + +The most common reason to get a touch device is to normalize inputs. This would look something like: + + SDL_Touch* inTouch = SDL_GetTouch(event.tfinger.touchId); + if(inTouch == NULL) continue; + + float x = ((float)event.tfinger.x)/inTouch->xres; + float y = ((float)event.tfinger.y)/inTouch->yres; + + +To get an SDL_Finger, call SDL_GetFinger(touch,fingerId), where touch is a pointer to an SDL_Touch device, and fingerId is the id of the requested finger. +This returns an SDL_Finger*, or null if the finger does not exist, or has been removed. +An SDL_Finger is guaranteed to be persistent for the duration of a touch, but it will be de-allocated as soon as the finger is removed. This occurs when the SDL_FINGERUP event is _added_ to the event queue, and thus BEFORE the FINGERUP event is seen. +As a result, be very careful to check for null return values. + +An SDL_Finger has the following fields: +>x,y,pressure: + The current coordinates of the touch. +>xdelta,ydelta: + The change in position resulting from the last finger motion. +>last_x, last_y, last_pressure: + The previous coordinates of the touch. \ No newline at end of file diff --git a/include/SDL_events.h b/include/SDL_events.h index f6603c1f1..eec314401 100644 --- a/include/SDL_events.h +++ b/include/SDL_events.h @@ -340,6 +340,7 @@ typedef struct SDL_DollarGestureEvent Uint32 windowID; /**< The window with mouse focus, if any */ SDL_TouchID touchId; /**< The touch device index */ SDL_GestureID gestureId; + Uint32 numFingers; float error; /* //TODO: Enable to give location? diff --git a/include/SDL_touch.h b/include/SDL_touch.h index 6c86a2d17..e6b3c9557 100644 --- a/include/SDL_touch.h +++ b/include/SDL_touch.h @@ -50,11 +50,11 @@ struct SDL_Finger { SDL_FingerID id; Uint16 x; Uint16 y; + Uint16 pressure; Uint16 xdelta; Uint16 ydelta; Uint16 last_x, last_y,last_pressure; /* the last reported coordinates */ SDL_bool down; - Uint16 pressure; }; typedef struct SDL_Touch SDL_Touch; diff --git a/src/events/SDL_gesture.c b/src/events/SDL_gesture.c index 93ded4d0f..44aa1bd25 100644 --- a/src/events/SDL_gesture.c +++ b/src/events/SDL_gesture.c @@ -462,6 +462,8 @@ int SDL_SendGestureDollar(SDL_GestureTouch* touch, */ event.dgesture.gestureId = gestureId; event.dgesture.error = error; + //A finger came up to trigger this event. + event.dgesture.numFingers = touch->numDownFingers + 1; return SDL_PushEvent(&event) > 0; } @@ -471,7 +473,6 @@ int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId) { event.dgesture.type = SDL_DOLLARRECORD; event.dgesture.touchId = touch->id; event.dgesture.gestureId = gestureId; - return SDL_PushEvent(&event) > 0; } diff --git a/src/events/SDL_touch.c b/src/events/SDL_touch.c index 56158142e..66c28412a 100644 --- a/src/events/SDL_touch.c +++ b/src/events/SDL_touch.c @@ -351,6 +351,7 @@ SDL_SendFingerDown(SDL_TouchID id, SDL_FingerID fingerid, SDL_bool down, event.tfinger.touchId = id; event.tfinger.x = x; event.tfinger.y = y; + event.tfinger.pressure = pressure; event.tfinger.state = touch->buttonstate; event.tfinger.windowID = touch->focus ? touch->focus->id : 0; event.tfinger.fingerId = fingerid; @@ -470,8 +471,7 @@ SDL_SendTouchMotion(SDL_TouchID id, SDL_FingerID fingerid, int relative, event.tfinger.x = x; event.tfinger.y = y; event.tfinger.dx = xrel; - event.tfinger.dy = yrel; - + event.tfinger.dy = yrel; event.tfinger.pressure = pressure; event.tfinger.state = touch->buttonstate; diff --git a/test/Makefile.in b/test/Makefile.in index 42677ecde..a8df4c4b0 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -7,7 +7,7 @@ EXE = @EXE@ CFLAGS = @CFLAGS@ LIBS = @LIBS@ -TARGETS = checkkeys$(EXE) graywin$(EXE) loopwave$(EXE) testalpha$(EXE) testatomic$(EXE) testaudioinfo$(EXE) testbitmap$(EXE) testblitspeed$(EXE) testcursor$(EXE) testdraw2$(EXE) testdyngles$(EXE) testdyngl$(EXE) testerror$(EXE) testfile$(EXE) testfill$(EXE) testgamma$(EXE) testgl2$(EXE) testgles$(EXE) testgl$(EXE) testhaptic$(EXE) testhread$(EXE) testiconv$(EXE) testime$(EXE) testintersections$(EXE) testjoystick$(EXE) testkeys$(EXE) testloadso$(EXE) testlock$(EXE) testmultiaudio$(EXE) testoverlay2$(EXE) testoverlay$(EXE) testpalette$(EXE) testplatform$(EXE) testpower$(EXE) testresample$(EXE) testsem$(EXE) testsprite2$(EXE) testsprite$(EXE) testspriteminimal$(EXE) testtimer$(EXE) testver$(EXE) testvidinfo$(EXE) testwin$(EXE) testwm2$(EXE) testwm$(EXE) threadwin$(EXE) torturethread$(EXE) +TARGETS = checkkeys$(EXE) graywin$(EXE) loopwave$(EXE) testalpha$(EXE) testatomic$(EXE) testaudioinfo$(EXE) testbitmap$(EXE) testblitspeed$(EXE) testcursor$(EXE) testdraw2$(EXE) testdyngles$(EXE) testdyngl$(EXE) testerror$(EXE) testfile$(EXE) testfill$(EXE) testgamma$(EXE) testgl2$(EXE) testgles$(EXE) testgl$(EXE) testhaptic$(EXE) testhread$(EXE) testiconv$(EXE) testime$(EXE) testintersections$(EXE) testjoystick$(EXE) testkeys$(EXE) testloadso$(EXE) testlock$(EXE) testmultiaudio$(EXE) testoverlay2$(EXE) testoverlay$(EXE) testpalette$(EXE) testplatform$(EXE) testpower$(EXE) testresample$(EXE) testsem$(EXE) testsprite2$(EXE) testsprite$(EXE) testspriteminimal$(EXE) testtimer$(EXE) testver$(EXE) testvidinfo$(EXE) testwin$(EXE) testwm2$(EXE) testwm$(EXE) threadwin$(EXE) torturethread$(EXE) testgesture$(EXE) all: Makefile $(TARGETS) @@ -151,6 +151,9 @@ testhaptic$(EXE): $(srcdir)/testhaptic.c testatomic$(EXE): $(srcdir)/testatomic.c $(CC) -o $@ $? $(CFLAGS) $(LIBS) + +testgesture$(EXE): $(srcdir)/testgesture.c + $(CC) -o $@ $? $(CFLAGS) $(LIBS) testime$(EXE): $(srcdir)/testime.c $(CC) -o $@ $? $(CFLAGS) $(LIBS) @SDL_TTF_LIB@ diff --git a/test/testgesture.c b/test/testgesture.c new file mode 100644 index 000000000..fe740a00b --- /dev/null +++ b/test/testgesture.c @@ -0,0 +1,297 @@ +/* Usage: + * Spacebar to begin recording a gesture on all touches. + * s to save all touches into "./gestureSave" + * l to load all touches from "./gestureSave" + */ + +#include +#include +#include +#include +#include + + +/* Make sure we have good macros for printing 32 and 64 bit values */ +#ifndef PRIs32 +#define PRIs32 "d" +#endif +#ifndef PRIu32 +#define PRIu32 "u" +#endif +#ifndef PRIs64 +#ifdef __WIN32__ +#define PRIs64 "I64" +#else +#define PRIs64 "lld" +#endif +#endif +#ifndef PRIu64 +#ifdef __WIN32__ +#define PRIu64 "I64u" +#else +#define PRIu64 "llu" +#endif +#endif + +#define WIDTH 640 +#define HEIGHT 480 +#define BPP 4 +#define DEPTH 32 + +//MUST BE A POWER OF 2! +#define EVENT_BUF_SIZE 256 + + +#define VERBOSE SDL_FALSE + +SDL_Event events[EVENT_BUF_SIZE]; +int eventWrite; + +int colors[7] = {0xFF,0xFF00,0xFF0000,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF}; + +typedef struct { + float x,y; +} Point; + +typedef struct { + float ang,r; + Point p; +} Knob; + +Knob knob; + +void handler (int sig) +{ + printf ("\exiting...(%d)\n", sig); + exit (0); +} + +void perror_exit (char *error) +{ + perror (error); + handler (9); +} + +void setpix(SDL_Surface *screen, int x, int y, unsigned int col) +{ + Uint32 *pixmem32; + Uint32 colour; + + if((unsigned)x > screen->w) return; + if((unsigned)y > screen->h) return; + + pixmem32 = (Uint32*) screen->pixels + y*screen->pitch/BPP + x; + + Uint8 r,g,b; + float a; + + memcpy(&colour,pixmem32,screen->format->BytesPerPixel); + + SDL_GetRGB(colour,screen->format,&r,&g,&b); + //r = 0;g = 0; b = 0; + a = (col>>24)&0xFF; + if(a == 0) a = 0xFF; //Hack, to make things easier. + a /= 0xFF; + r = r*(1-a) + ((col>>16)&0xFF)*(a); + g = g*(1-a) + ((col>> 8)&0xFF)*(a); + b = b*(1-a) + ((col>> 0)&0xFF)*(a); + colour = SDL_MapRGB( screen->format,r, g, b); + + + *pixmem32 = colour; +} + +void drawLine(SDL_Surface *screen,int x0,int y0,int x1,int y1,unsigned int col) { + float t; + for(t=0;t<1;t+=1.f/SDL_max(abs(x0-x1),abs(y0-y1))) + setpix(screen,x1+t*(x0-x1),y1+t*(y0-y1),col); +} + +void drawCircle(SDL_Surface* screen,int x,int y,int r,unsigned int c) +{ + int tx,ty; + float xr; + for(ty = -abs(r);ty <= abs(r);ty++) { + xr = sqrt(r*r - ty*ty); + if(r > 0) { //r > 0 ==> filled circle + for(tx=-xr+.5;tx<=xr-.5;tx++) { + setpix(screen,x+tx,y+ty,c); + } + } + else { + setpix(screen,x-xr+.5,y+ty,c); + setpix(screen,x+xr-.5,y+ty,c); + } + } +} + +void drawKnob(SDL_Surface* screen,Knob k) { + drawCircle(screen,k.p.x*screen->w,k.p.y*screen->h,k.r*screen->w,0xFFFFFF); + drawCircle(screen,(k.p.x+k.r/2*cos(k.ang))*screen->w, + (k.p.y+k.r/2*sin(k.ang))*screen->h,k.r/4*screen->w,0); +} + +void DrawScreen(SDL_Surface* screen) +{ + int x, y; + if(SDL_MUSTLOCK(screen)) + { + if(SDL_LockSurface(screen) < 0) return; + } + for(y = 0;y < screen->h;y++) + for(x = 0;x < screen->w;x++) + setpix(screen,x,y,((x%255)<<16) + ((y%255)<<8) + (x+y)%255); + + int i; + //draw Touch History + for(i = SDL_max(0,eventWrite - EVENT_BUF_SIZE);i < eventWrite;i++) { + SDL_Event event = events[i&(EVENT_BUF_SIZE-1)]; + int age = eventWrite - i - 1; + if(event.type == SDL_FINGERMOTION || + event.type == SDL_FINGERDOWN || + event.type == SDL_FINGERUP) { + SDL_Touch* inTouch = SDL_GetTouch(event.tfinger.touchId); + if(inTouch == NULL) continue; + + float x = ((float)event.tfinger.x)/inTouch->xres; + float y = ((float)event.tfinger.y)/inTouch->yres; + + //draw the touch: + unsigned int c = colors[event.tfinger.touchId%7]; + unsigned int col = + ((unsigned int)(c*(.1+.85))) | + ((unsigned int)((0xFF*(1-((float)age)/EVENT_BUF_SIZE))) & 0xFF)<<24; + + if(event.type == SDL_FINGERMOTION) + drawCircle(screen,x*screen->w,y*screen->h,5,col); + else if(event.type == SDL_FINGERDOWN) + drawCircle(screen,x*screen->w,y*screen->h,-10,col); + } + } + + if(knob.p.x > 0) + drawKnob(screen,knob); + + if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); + SDL_Flip(screen); +} + +SDL_Surface* initScreen(int width,int height) +{ + return SDL_SetVideoMode(width, height, DEPTH, + SDL_HWSURFACE | SDL_RESIZABLE); +} + +int main(int argc, char* argv[]) +{ + SDL_Surface *screen; + SDL_Event event; + + //gesture variables + knob.r = .1; + knob.ang = 0; + + + SDL_bool quitting = SDL_FALSE; + SDL_RWops *src; + + if (SDL_Init(SDL_INIT_VIDEO) < 0 ) return 1; + + if (!(screen = initScreen(WIDTH,HEIGHT))) + { + SDL_Quit(); + return 1; + } + + while(!quitting) { + while(SDL_PollEvent(&event)) + { + //Record _all_ events + events[eventWrite & (EVENT_BUF_SIZE-1)] = event; + eventWrite++; + + switch (event.type) + { + case SDL_QUIT: + quitting = SDL_TRUE; + break; + case SDL_KEYDOWN: + switch (event.key.keysym.sym) + { + case SDLK_SPACE: + SDL_RecordGesture(-1); + break; + case SDLK_s: + src = SDL_RWFromFile("gestureSave","w"); + printf("Wrote %i templates\n",SDL_SaveAllDollarTemplates(src)); + SDL_RWclose(src); + break; + case SDLK_l: + src = SDL_RWFromFile("gestureSave","r"); + printf("Loaded: %i\n",SDL_LoadDollarTemplates(-1,src)); + SDL_RWclose(src); + break; + case SDLK_ESCAPE: + quitting = SDL_TRUE; + break; + } + break; + case SDL_VIDEORESIZE: + if (!(screen = initScreen(event.resize.w, + event.resize.h))) + { + SDL_Quit(); + return 1; + } + break; + case SDL_FINGERMOTION: + ; +#if VERBOSE + printf("Finger: %i,x: %i, y: %i\n",event.tfinger.fingerId, + event.tfinger.x,event.tfinger.y); +#endif + SDL_Touch* inTouch = SDL_GetTouch(event.tfinger.touchId); + SDL_Finger* inFinger = SDL_GetFinger(inTouch,event.tfinger.fingerId); + break; + case SDL_FINGERDOWN: +#if VERBOSE + printf("Finger: %"PRIs64" down - x: %i, y: %i\n", + event.tfinger.fingerId,event.tfinger.x,event.tfinger.y); +#endif + break; + case SDL_FINGERUP: +#if VERBOSE + printf("Finger: %"PRIs64" up - x: %i, y: %i\n", + event.tfinger.fingerId,event.tfinger.x,event.tfinger.y); +#endif + break; + case SDL_MULTIGESTURE: +#if VERBOSE + printf("Multi Gesture: x = %f, y = %f, dAng = %f, dR = %f\n", + event.mgesture.x, + event.mgesture.y, + event.mgesture.dTheta, + event.mgesture.dDist); + printf("MG: numDownTouch = %i\n",event.mgesture.numFingers); +#endif + knob.p.x = event.mgesture.x; + knob.p.y = event.mgesture.y; + knob.ang += event.mgesture.dTheta; + knob.r += event.mgesture.dDist; + break; + case SDL_DOLLARGESTURE: + printf("Gesture %"PRIs64" performed, error: %f\n", + event.dgesture.gestureId, + event.dgesture.error); + break; + case SDL_DOLLARRECORD: + printf("Recorded gesture: %"PRIs64"\n",event.dgesture.gestureId); + break; + } + } + DrawScreen(screen); + } + SDL_Quit(); + return 0; +} + diff --git a/touchTest/gestureSDLTest.c b/touchTest/gestureSDLTest.c index e4f8ecc65..fe740a00b 100644 --- a/touchTest/gestureSDLTest.c +++ b/touchTest/gestureSDLTest.c @@ -1,9 +1,16 @@ +/* Usage: + * Spacebar to begin recording a gesture on all touches. + * s to save all touches into "./gestureSave" + * l to load all touches from "./gestureSave" + */ + #include #include #include #include #include + /* Make sure we have good macros for printing 32 and 64 bit values */ #ifndef PRIs32 #define PRIs32 "d"