From ed49e7a6f97a3937a93cc799c4cdec7ad6dbd960 Mon Sep 17 00:00:00 2001 From: Jim Grandpre Date: Wed, 7 Jul 2010 04:13:08 -0700 Subject: [PATCH] Moved Multi finger gesture recognition into the library. --- include/SDL_events.h | 29 +- src/events/SDL_events.c | 4 + src/events/SDL_events_c.h | 2 +- src/events/SDL_gesture.c | 189 ++++++++++ src/events/SDL_gesture_c.h | 32 ++ src/events/SDL_touch.c | 3 + touchTest/gestureSDLTest.c | 687 +++++++++++++++++++++++++++++++++++++ touchTest/makefile | 1 + touchTest/touchPong | Bin 29219 -> 29499 bytes touchTest/touchSimp | Bin 29024 -> 29308 bytes 10 files changed, 945 insertions(+), 2 deletions(-) create mode 100644 src/events/SDL_gesture.c create mode 100644 src/events/SDL_gesture_c.h create mode 100644 touchTest/gestureSDLTest.c diff --git a/include/SDL_events.h b/include/SDL_events.h index 0e19359b0..fe6109271 100644 --- a/include/SDL_events.h +++ b/include/SDL_events.h @@ -86,13 +86,17 @@ typedef enum SDL_JOYBUTTONDOWN, /**< Joystick button pressed */ SDL_JOYBUTTONUP, /**< Joystick button released */ - /*Touch events - is 0x700 the correct place?*/ + /*Touch events*/ SDL_FINGERDOWN = 0x700, SDL_FINGERUP, SDL_FINGERMOTION, SDL_TOUCHBUTTONDOWN, SDL_TOUCHBUTTONUP, + /*Gesture events*/ + SDL_DOLLARGESTURE = 0x800, + SDL_MULTIGESTURE, + /* Obsolete events */ SDL_EVENT_COMPAT1 = 0x7000, /**< SDL 1.2 events for compatibility */ SDL_EVENT_COMPAT2, @@ -331,6 +335,28 @@ typedef struct SDL_TouchButtonEvent } SDL_TouchButtonEvent; + +/** + * \brief Multiple Finger Gesture Event + */ +typedef struct SDL_MultiGestureEvent +{ + Uint32 type; /**< ::SDL_MULTIGESTURE */ + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint8 touchId; /**< The touch device index */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + float dTheta; + float dDist; + float x; //currently 0...1. Change to screen coords? + float y; + +} SDL_MultiGestureEvent; + + + + /** * \brief The "quit requested" event */ @@ -416,6 +442,7 @@ typedef union SDL_Event SDL_ProximityEvent proximity; /**< Proximity In or Out event */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ SDL_TouchButtonEvent tbutton; /**< Touch button event data */ + SDL_MultiGestureEvent mgesture; /**< Multi Finger Gesture data*/ /** Temporarily here for backwards compatibility */ /*@{*/ diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index ae82171b9..796a5551a 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -493,6 +493,10 @@ SDL_PushEvent(SDL_Event * event) if (SDL_PeepEvents(event, 1, SDL_ADDEVENT, 0, 0) <= 0) { return -1; } + + SDL_GestureProcessEvent(event); + + return 1; } diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index ea1ec445b..ccf29b3da 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -28,7 +28,7 @@ #include "SDL_keyboard_c.h" #include "SDL_touch_c.h" #include "SDL_windowevents_c.h" - +#include "SDL_gesture_c.h" /* Start and stop the event processing loop */ extern int SDL_StartEventLoop(Uint32 flags); extern void SDL_StopEventLoop(void); diff --git a/src/events/SDL_gesture.c b/src/events/SDL_gesture.c new file mode 100644 index 000000000..4ef644faa --- /dev/null +++ b/src/events/SDL_gesture.c @@ -0,0 +1,189 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2010 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ +#include "SDL_config.h" + +/* General mouse handling code for SDL */ + +#include "SDL_events.h" +#include "SDL_events_c.h" +#include "SDL_gesture_c.h" + +//TODO: Replace with malloc +#define MAXFINGERS 3 +#define MAXTOUCHES 2 + +typedef struct { + float x,y; +} Point; + + +typedef struct { + Point p; + float pressure; + int id; +} Finger; + +typedef struct { + Finger f; + Point cv; + float dtheta,dDist; +} TouchPoint; + + +typedef struct { + int id; + Point res; + Point centroid; + TouchPoint gestureLast[MAXFINGERS]; + int numDownFingers; +} GestureTouch; + +GestureTouch gestureTouch[MAXTOUCHES]; +int numGestureTouches = 0; +int SDL_GestureAddTouch(SDL_Touch* touch) { + if(numGestureTouches >= MAXTOUCHES) return -1; + + gestureTouch[numGestureTouches].res.x = touch->xres; + gestureTouch[numGestureTouches].res.y = touch->yres; + gestureTouch[numGestureTouches].numDownFingers = 0; + + gestureTouch[numGestureTouches].res.x = touch->xres; + gestureTouch[numGestureTouches].id = touch->id; + + numGestureTouches++; + return 0; +} + +GestureTouch * SDL_GetGestureTouch(int id) { + int i; + for(i = 0;i < numGestureTouches; i++) { + //printf("%i ?= %i\n",gestureTouch[i].id,id); + if(gestureTouch[i].id == id) return &gestureTouch[i]; + } + return NULL; +} + +int SDL_SendGestureMulti(GestureTouch* touch,float dTheta,float dDist) { + SDL_Event event; + event.mgesture.type = SDL_MULTIGESTURE; + event.mgesture.touchId = touch->id; + event.mgesture.x = touch->centroid.x; + event.mgesture.y = touch->centroid.y; + event.mgesture.dTheta = dTheta; + event.mgesture.dDist = dDist; + return SDL_PushEvent(&event) > 0; +} + +void SDL_GestureProcessEvent(SDL_Event* event) +{ + if(event->type == SDL_FINGERMOTION || + event->type == SDL_FINGERDOWN || + event->type == SDL_FINGERUP) { + GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId); + + + //Shouldn't be possible + if(inTouch == NULL) return; + + + float x = ((float)event->tfinger.x)/inTouch->res.x; + float y = ((float)event->tfinger.y)/inTouch->res.y; + int j,empty = -1; + + for(j = 0;jnumDownFingers;j++) { + if(inTouch->gestureLast[j].f.id != event->tfinger.fingerId) continue; + + if(event->type == SDL_FINGERUP) { + inTouch->numDownFingers--; + inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers]; + break; + } + else { + float dx = x - inTouch->gestureLast[j].f.p.x; + float dy = y - inTouch->gestureLast[j].f.p.y; + inTouch->centroid.x += dx/inTouch->numDownFingers; + inTouch->centroid.y += dy/inTouch->numDownFingers; + if(inTouch->numDownFingers > 1) { + Point lv; //Vector from centroid to last x,y position + Point v; //Vector from centroid to current x,y position + lv = inTouch->gestureLast[j].cv; + float lDist = sqrt(lv.x*lv.x + lv.y*lv.y); + //printf("lDist = %f\n",lDist); + v.x = x - inTouch->centroid.x; + v.y = y - inTouch->centroid.y; + inTouch->gestureLast[j].cv = v; + float Dist = sqrt(v.x*v.x+v.y*v.y); + // cos(dTheta) = (v . lv)/(|v| * |lv|) + + //Normalize Vectors to simplify angle calculation + lv.x/=lDist; + lv.y/=lDist; + v.x/=Dist; + v.y/=Dist; + float dtheta = atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y); + + float dDist = (Dist - lDist); + if(lDist == 0) {dDist = 0;dtheta = 0;} //To avoid impossible values + inTouch->gestureLast[j].dDist = dDist; + inTouch->gestureLast[j].dtheta = dtheta; + + //printf("dDist = %f, dTheta = %f\n",dDist,dtheta); + //gdtheta = gdtheta*.9 + dtheta*.1; + //gdDist = gdDist*.9 + dDist*.1 + //knob.r += dDist/numDownFingers; + //knob.ang += dtheta; + //printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist); + //printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); + SDL_SendGestureMulti(inTouch,dtheta,dDist); + } + else { + inTouch->gestureLast[j].dDist = 0; + inTouch->gestureLast[j].dtheta = 0; + inTouch->gestureLast[j].cv.x = 0; + inTouch->gestureLast[j].cv.y = 0; + } + inTouch->gestureLast[j].f.p.x = x; + inTouch->gestureLast[j].f.p.y = y; + break; + //pressure? + } + } + + if(j == inTouch->numDownFingers) { + //printf("Finger Down!!!\n"); + inTouch->numDownFingers++; + inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+ + x)/inTouch->numDownFingers; + inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+ + y)/inTouch->numDownFingers; + + inTouch->gestureLast[j].f.id = event->tfinger.fingerId; + inTouch->gestureLast[j].f.p.x = x; + inTouch->gestureLast[j].f.p.y = y; + inTouch->gestureLast[j].cv.x = 0; + inTouch->gestureLast[j].cv.y = 0; + } + } +} + + /* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/events/SDL_gesture_c.h b/src/events/SDL_gesture_c.h new file mode 100644 index 000000000..b6513e5cc --- /dev/null +++ b/src/events/SDL_gesture_c.h @@ -0,0 +1,32 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2010 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ +#include "SDL_config.h" + +#ifndef _SDL_gesture_c_h +#define _SDL_gesture_c_h + +extern void SDL_GestureProcessEvent(SDL_Event* event); + + +#endif /* _SDL_gesture_c_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/events/SDL_touch.c b/src/events/SDL_touch.c index 1511861d1..cc5914037 100644 --- a/src/events/SDL_touch.c +++ b/src/events/SDL_touch.c @@ -140,6 +140,9 @@ SDL_AddTouch(const SDL_Touch * touch, char *name) SDL_touchPads[index]->relative_mode = SDL_FALSE; SDL_touchPads[index]->flush_motion = SDL_FALSE; + //Do I want this here? Probably + SDL_GestureAddTouch(SDL_touchPads[index]); + return index; } diff --git a/touchTest/gestureSDLTest.c b/touchTest/gestureSDLTest.c new file mode 100644 index 000000000..cedb14205 --- /dev/null +++ b/touchTest/gestureSDLTest.c @@ -0,0 +1,687 @@ +#include +#include +#include +#include + +#define PI 3.1415926535897 +#define PHI ((sqrt(5)-1)/2) +#define WIDTH 640 +#define HEIGHT 480 +#define BPP 4 +#define DEPTH 32 + +#define MAXFINGERS 3 + +#define DOLLARNPOINTS 64 +#define DOLLARSIZE 256 + +//MUST BE A POWER OF 2! +#define EVENT_BUF_SIZE 256 + +SDL_Event events[EVENT_BUF_SIZE]; +int eventWrite; + +int mousx,mousy; +int keystat[512]; +int bstatus; + +int colors[7] = {0xFF,0xFF00,0xFF0000,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF}; + +int index2fingerid[MAXFINGERS]; +int fingersDown; + +typedef struct { + float x,y; +} Point; + +typedef struct { + Point p; + float pressure; + int id; +} Finger; + +typedef struct { + Finger f; + Point cv; + float dtheta,dDist; +} TouchPoint; + + +typedef struct { //dt + s + Point d,s; //direction, start + int points; +} Line; + + +typedef struct { + float length; + + int numPoints; + Point p[EVENT_BUF_SIZE]; //To be safe +} DollarPath; + +typedef struct { + float ang,r; + Point p; +} Knob; + +Knob knob; + +Finger finger[MAXFINGERS]; + + +DollarPath dollarPath[MAXFINGERS]; + +#define MAXTEMPLATES 4 + +Point dollarTemplate[MAXTEMPLATES][DOLLARNPOINTS]; +int numDollarTemplates = 0; +#ifdef DRAW_VECTOR_EST +Line gestureLine[MAXFINGERS]; +#endif + +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); //Always returns 0xFFFFFF? + //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) +{ + + float a; + int tx; + + int 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) { + //printf("Knob: x = %f, y = %f, r = %f, a = %f\n",k.p.x,k.p.y,k.r,k.ang); + + 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 drawDollarPath(SDL_Surface* screen,Point* points,int numPoints, + int rad,unsigned int col){ + int i; + for(i=0;iw/2, + points[i].y+screen->h/2, + rad,col); + } +} + +float dollarDifference(Point* points,Point* templ,float ang) { + // Point p[DOLLARNPOINTS]; + float dist = 0; + Point p; + int i; + for(i = 0; i < DOLLARNPOINTS; i++) { + p.x = points[i].x * cos(ang) - points[i].y * sin(ang); + p.y = points[i].x * sin(ang) + points[i].y * cos(ang); + dist += sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+ + (p.y-templ[i].y)*(p.y-templ[i].y)); + } + return dist/DOLLARNPOINTS; + +} + +float bestDollarDifference(Point* points,Point* templ) { + //------------BEGIN DOLLAR BLACKBOX----------------// + //-TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-// + //-"http://depts.washington.edu/aimgroup/proj/dollar/"-// + float ta = -PI/4; + float tb = PI/4; + float dt = PI/90; + float x1 = PHI*ta + (1-PHI)*tb; + float f1 = dollarDifference(points,templ,x1); + float x2 = (1-PHI)*ta + PHI*tb; + float f2 = dollarDifference(points,templ,x2); + while(abs(ta-tb) > dt) { + if(f1 < f2) { + tb = x2; + x2 = x1; + f2 = f1; + x1 = PHI*ta + (1-PHI)*tb; + f1 = dollarDifference(points,templ,x1); + } + else { + ta = x1; + x1 = x2; + f1 = f2; + x2 = (1-PHI)*ta + PHI*tb; + f2 = dollarDifference(points,templ,x2); + } + } + /* + if(f1 <= f2) + printf("Min angle (x1): %f\n",x1); + else if(f1 > f2) + printf("Min angle (x2): %f\n",x2); + */ + return SDL_min(f1,f2); +} + +float dollarRecognize(SDL_Surface* screen, DollarPath path,int *bestTempl) { + + Point points[DOLLARNPOINTS]; + int numPoints = dollarNormalize(path,points); + int i; + + int k; + /* + for(k = 0;k interval) { + points[numPoints].x = path.p[i-1].x + + ((interval-dist)/d)*(path.p[i].x-path.p[i-1].x); + points[numPoints].y = path.p[i-1].y + + ((interval-dist)/d)*(path.p[i].y-path.p[i-1].y); + centroid.x += points[numPoints].x; + centroid.y += points[numPoints].y; + numPoints++; + + dist -= interval; + } + dist += d; + } + if(numPoints < 1) return 0; + centroid.x /= numPoints; + centroid.y /= numPoints; + + //printf("Centroid (%f,%f)",centroid.x,centroid.y); + //Rotate Points so point 0 is left of centroid and solve for the bounding box + float xmin,xmax,ymin,ymax; + xmin = centroid.x; + xmax = centroid.x; + ymin = centroid.y; + ymax = centroid.y; + + float ang = atan2(centroid.y - points[0].y, + centroid.x - points[0].x); + + for(i = 0;i xmax) xmax = points[i].x; + if(points[i].y < ymin) ymin = points[i].y; + if(points[i].y > ymax) ymax = points[i].y; + } + + //Scale points to DOLLARSIZE, and translate to the origin + float w = xmax-xmin; + float h = ymax-ymin; + + for(i=0;ih; y++ ) + { + for( x = 0; x < screen->w; x++ ) + { + //setpixel(screen, x, y, (x*x)/256+3*y+h, (y*y)/256+x+h, h); + //xm = (x+h)%screen->w; + //ym = (y+h)%screen->w; + //c = sin(h/256*2*PI)*x*y/screen->w/screen->h; + //setpix(screen,x,y,255*sin(xm/screen->w*2*PI),sin(h/255*2*PI)*255*y/screen->h,c); + setpix(screen,x,y,((x%255)<<16) + ((y%255)<<8) + (x+y)%255); + //setpix(screen,x,y,0); //Inefficient, but that's okay... + } + } + drawCircle(screen,mousx,mousy,-30,0xFFFFFF); + drawLine(screen,0,0,screen->w,screen->h,0xFFFFFF); + + int i; +//draw Touch History + TouchPoint gestureLast[MAXFINGERS]; + //printf("------------------Start History------------------\n"); + for(i = 0;i < MAXFINGERS;i++) { + gestureLast[i].f.id = -1; + } + int numDownFingers = 0; + Point centroid; + float gdtheta,gdDist; + + + 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); + //SDL_Finger* inFinger = SDL_GetFinger(inTouch,event.tfinger.fingerId); + + float x = ((float)event.tfinger.x)/inTouch->xres; + float y = ((float)event.tfinger.y)/inTouch->yres; + int j,empty = -1; + + for(j = 0;j= 0){ + drawDollarPath(screen,dollarTemplate[bestTempl] + ,DOLLARNPOINTS,-15,0x0066FF); + printf("Dollar error: %f\n",error); + } + + } + else if(numDollarTemplates < MAXTEMPLATES) { + + dollarNormalize(dollarPath[j], + dollarTemplate[numDollarTemplates]); + /* + int k; + for(k = 0;k 1) { + Point lv; //Vector from centroid to last x,y position + Point v; //Vector from centroid to current x,y position + lv.x = gestureLast[j].cv.x; + lv.y = gestureLast[j].cv.y; + float lDist = sqrt(lv.x*lv.x + lv.y*lv.y); + + v.x = x - centroid.x; + v.y = y - centroid.y; + gestureLast[j].cv = v; + float Dist = sqrt(v.x*v.x+v.y*v.y); + // cos(dTheta) = (v . lv)/(|v| * |lv|) + + lv.x/=lDist; + lv.y/=lDist; + v.x/=Dist; + v.y/=Dist; + float dtheta = atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y); + + float dDist = (lDist - Dist); + + gestureLast[j].dDist = dDist; + gestureLast[j].dtheta = dtheta; + + //gdtheta = gdtheta*.9 + dtheta*.1; + //gdDist = gdDist*.9 + dDist*.1 + gdtheta += dtheta; + gdDist += dDist; + + //printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist); + //printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist); + } + else { + gestureLast[j].dDist = 0; + gestureLast[j].dtheta = 0; + gestureLast[j].cv.x = 0; + gestureLast[j].cv.y = 0; + } + gestureLast[j].f.p.x = x; + gestureLast[j].f.p.y = y; + break; + //pressure? + } + } + else if(gestureLast[j].f.id == -1 && empty == -1) { + empty = j; + } + } + + if(j >= MAXFINGERS && empty >= 0) { + // printf("Finger Down!!!\n"); + numDownFingers++; + centroid.x = (centroid.x*(numDownFingers - 1) + x)/numDownFingers; + centroid.y = (centroid.y*(numDownFingers - 1) + y)/numDownFingers; + + j = empty; + gestureLast[j].f.id = event.tfinger.fingerId; + gestureLast[j].f.p.x = x; + gestureLast[j].f.p.y = y; + + + dollarPath[j].length = 0; + dollarPath[j].p[0].x = x; + dollarPath[j].p[0].y = y; + dollarPath[j].numPoints = 1; + } + + //draw the touch: + + if(gestureLast[j].f.id < 0) continue; //Finger up. Or some error... + + unsigned int c = colors[gestureLast[j].f.id%7]; + unsigned int col = + ((unsigned int)(c*(.1+.85))) | + ((unsigned int)((0xFF*(1-((float)age)/EVENT_BUF_SIZE))) & 0xFF)<<24; + x = gestureLast[j].f.p.x; + y = gestureLast[j].f.p.y; + 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 there is a centroid, draw it + if(numDownFingers > 1) { + unsigned int col = + ((unsigned int)(0xFFFFFF)) | + ((unsigned int)((0xFF*(1-((float)age)/EVENT_BUF_SIZE))) & 0xFF)<<24; + drawCircle(screen,centroid.x*screen->w,centroid.y*screen->h,5,col); + } + } + } + + + for(i=0;i= 0 && finger[i].p.y >= 0) + if(finger[i].pressure > 0) + drawCircle(screen,finger[i].p.x*screen->w,finger[i].p.y*screen->h + ,20,0xFF*finger[i].pressure); + else + drawCircle(screen,finger[i].p.x*screen->w,finger[i].p.y*screen->h + ,20,0xFF); + + + + keystat[32] = 0; + + 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; + + int keypress = 0; + int h=0,s=1,i,j; + + //gesture variables + int numDownFingers = 0; + float gdtheta = 0,gdDist = 0; + Point centroid; + knob.r = .1; + knob.ang = 0; + TouchPoint gestureLast[MAXFINGERS]; + + + + memset(keystat,0,512*sizeof(keystat[0])); + if (SDL_Init(SDL_INIT_VIDEO) < 0 ) return 1; + + if (!(screen = initScreen(WIDTH,HEIGHT))) + { + SDL_Quit(); + return 1; + } + + while(!keystat[27]) { + //Poll SDL + while(SDL_PollEvent(&event)) + { + //Record _all_ events + events[eventWrite & (EVENT_BUF_SIZE-1)] = event; + eventWrite++; + + switch (event.type) + { + case SDL_QUIT: + keystat[27] = 1; + break; + case SDL_KEYDOWN: + //printf("%i\n",event.key.keysym.sym); + keystat[event.key.keysym.sym] = 1; + //keypress = 1; + break; + case SDL_KEYUP: + //printf("%i\n",event.key.keysym.sym); + keystat[event.key.keysym.sym] = 0; + //keypress = 1; + break; + case SDL_VIDEORESIZE: + if (!(screen = initScreen(event.resize.w, + event.resize.h))) + { + SDL_Quit(); + return 1; + } + break; + case SDL_MOUSEMOTION: + mousx = event.motion.x; + mousy = event.motion.y; + break; + case SDL_MOUSEBUTTONDOWN: + bstatus |= (1<<(event.button.button-1)); + break; + case SDL_MOUSEBUTTONUP: + bstatus &= ~(1<<(event.button.button-1)); + break; + case SDL_FINGERMOTION: + ; + //printf("Finger: %i,x: %i, y: %i\n",event.tfinger.fingerId, + // event.tfinger.x,event.tfinger.y); + SDL_Touch* inTouch = SDL_GetTouch(event.tfinger.touchId); + SDL_Finger* inFinger = SDL_GetFinger(inTouch,event.tfinger.fingerId); + + for(i = 0;i 0) { + finger[i].p.x = ((float)event.tfinger.x)/ + inTouch->xres; + finger[i].p.y = ((float)event.tfinger.y)/ + inTouch->yres; + + finger[i].pressure = + ((float)event.tfinger.pressure)/inTouch->pressureres; + /* + printf("Finger: %i, Pressure: %f Pressureres: %i\n", + event.tfinger.fingerId, + finger[i].pressure, + inTouch->pressureres); + */ + //printf("Finger: %i, pressure: %f\n",event.tfinger.fingerId, + // finger[event.tfinger.fingerId].pressure); + } + + break; + case SDL_FINGERDOWN: + printf("Finger: %i down - x: %i, y: %i\n",event.tfinger.fingerId, + event.tfinger.x,event.tfinger.y); + + for(i = 0;i%=p|9}2-?yX9_bd|PW zr^OAl=40M$^dM!N&itrx^`Xz2Pa1x*B=gU{x4QH=+@z+O8rF}T$X4o$Or=VS%Ey)! zS12n>sma9^6_YAP70)OQt9;l~io=sjXQ=h)j;$z~-n+D7Oj)r?tF2TYm_x@@6c?AP z#V||DOT(Ud5az_9(sI=v023!ooiao9^?EZ^33?NXXHE$hMZ#(pjM3 zIL8_6?F{-KaRzg&)%)(7E##;tudq1ZZ%5%$mFh9uv7WQkJBGdZ6zb zQi=(hDZUDB#M4vZP^N~Qm#Gz?p*S}|rtS_2*0b=Cz+b0fLg%q?S9J@4L2lxbjT-4x1knnLw2T(ZG!9q$W}3Y}jAo{uNv z*UvA(Nq|2DMMD`I?RB|#So;xl;442^YT)PFX#Xv@8$D>ZY}`HaD}WmWZv6ya)^ZRE zDEbn3$c@zi@db$7ZMroY-UCRkhfS9CE%Fjb z_RJiWDq#guCwc%XL z?O;|SS>_0*VsrD&2cfAs()H36f{E@a{SY7p^mOd-2{m)ny>J!O6I;&*IsO_$Dw&%C zQs<8N_3;4v#im24%KoA0V?}#FtOa+6<61bPcS2{S+H)yRKnFgH@s7L&V|VddjQoyApoa2!qkXd3(8v>zWS`>uFuh-K z9y}dToCD$?C@w?Y98~-OtPd4m0@fkLtw8G|#q;3xu;TmC9KifNrGWTY@d*^pCyKA3Iji`eXg*bJ!rM8; z*=WuyZjI(M#of_d04|z~ipQY2q zWEmFTK$sZ&M)657`xddo`c82^c7G2X2!2y>FYx?9aRKD{r{V|{`!Api6Hmt3T|xXjZiSV^Rj9C4#1+Wy3&g9C*45w* zqHBo%gt@;E_eG#QkfzDcZM$y>x9BI+9A z$J10qZ6y95Kx`sjh-NeKKxE`?1OxT95NE>oR^pv-x{cTm^|ljFg|!30pxH^h0?jVs z*U`K~oQ1@^OMDF0ZsJ-9bPw@C1o$4|f>!E%;v0x_FL541itIx$IGOv2TOfxAh~ELl z4?q!FJ4k#Ma~~4lM8O{-&P7Zg5&s=T4->aW6i0|(1>d8@gAwR4;#AN+PP`tzPY{0v zFDHphVVxp=7jvhH)3Ml^~nWJ$f;OA=G z5<1P(?EZNvk=4t8^X-?v;LSgv?a=c|Hbb`}q#6oa5*BvA5FT z8)&Kw{tQ~qHh2lbn`7`c2vM2HV{%kORh7s~WNDB;MJQE4J}N>7xgH{!6XaX=(bgG} zH?VJZGJk`_%t_|jP;PEAmqM0Gi_0-rWpNEctwetJqOC%E2JLLLpF`M6Ft32>w{9gU zqaDJ@;F)qYn=3$sXGNc88Q8*)x|VMC#sdB?=lLDUHO%j!H$Ub+&P{VPxbow!m1s^7 zE8<#7W+Ujpl?8y&a)V}bv8r6l!Yi7HIa`D^dZL@dPacdBc`A;`f+&$!l5xTCLf5@v z4inDRQ6dhlMNuLSt*4_z1|XUIj3gUGluz(@@d&^fDY)`F@Upv5QLYBdiqH%7DgWenm{dN@E zQFx1MnUeU3;;m6=N8)WBbn*#S-Xm5-%WZC67xO)LGSBqkc<}qK)z)l-dSd5~2YH9; zgUgBcxpVo-CnE6v?V?9A3N?d2>V@REc|RPd$B{Tajz;zPJc0wq$D)Eb@*I!q;pFMW zZF-z^fgL?gxqZ~VAoFSYYs>f&=Qe}S?Te8(A4lS|I1(4cGvfCS;Ke8jCk>aPB%FG< zd>e@?w~@FSBO%CqZ3uvkW;o7S{QtvoJGe^xEsn(3aU>#tcU^lV{t-vwMjVN6JQ8k# zzKzmAoqXrfkUII^?Pn;T@Zg)rgwi0e3-ZtVVw8S~Qi{AHDg4!St((b`-``xz=Uz;= zT+44cx+w4MKVbF7y~{){oS{+$gEblhj5GsTwb-D6C@jml2~F=W0%bXX(LW;eh&5Gc zCV6gsmXq|LXL_I<19hq~ISOhy<(T4`L6s`FY|(MyFitK)u0`u(95UlMWQo@ZZXZ3p zq$sSoc8r4)TAdgNhf>2R2bC(Gxe*P6TU5<-6w}?FVOh>i)7>*ORH`uI9#kKM(#WZ<{?r%;(bZr;JiCGM z>;~P&PVn!E(Q_mkd>cK?4vDf;sRG0Wv_n9zq{t)gAvziZ9Uc$*`*_el#6X>Nj_}Ng z>DKkbF;M5gKN1DC3gtE&>A>JoXQhaMqde0R!T#tO86sGbXJp|cjeZwWmnuAY47CN# zw4HdL(7B8h9^9@_6C+X5Dph8=oUX{qpYT4O!C@+nt(+QNXgLX=7K`p3smAGEW(>=D zMVLWzohNC~a^4YUdZwjP#WByKApE8d!!semkI`c=QpBLN5qJYIcpm*VhC4rsn?7;M zc*RdRQ#dpDlljl$M0W6#Gza~(zRvEY^h<2_^U>YbY3?e$uDo+3y95bF26-(a2S*Y~!_LU{}Ubxa-QV(J;KIgq$1b zb;m_0M{wRCc}J^-k9>Ikt|NI*3KhsJ&3cj-myq+=|0~IRMQDN?+YKZS`jAtfZ<0LZ zLRE4C-XeL{gjlZ48j|}p6qW1+p(`0#X+CD zx-^T(_wEL=;ufj;JyDvJ_7H~1m#}t&Zq%QKoKw1x#YiRu2j7&FL42rhfT)o~AYh2HsB{tn z%BElu7Fh)3fy>hYBMgW;11KUO=%9cKxQr;H;LL!7gUo-bs`J_TzVyB4F6aNxa?h=* zZQs(fU(@`yntk`i_P0~!C$*nNPF3I7ZTI^0)7#36Cgt|%{imk6)UHh|l%rI3mQodE zRrii9uU6NhKcT$3dVKZB^2wDossi%F@|p>ilhqgK-dkNZ>4wVcd&ZWlKV&P_2jb9s zs>{o()GmmXRh2cqNG+8bS5{f2?r(+8_y;FWR*(3-De4*Y#*|N)SW_0OQ5zxLjc(0@ z6V*ZV@2jjD4IYU$sNXcISd^NYH)239V=t)zl2Uw}Kvvf0~QHO*DdeCZt zhRlH$8ZB6y>kjJfVBjWq&~U&wmZTe%RW(X8jd!3;GlLf+veeiKN!No$1V(F(_d=<0 zBpuzw(hcZAeXih8U?_AMOF$}V#~_TDsmb`!`|U#Cda*Th^skbIQDq$tLmDu}m2?%7 z{fCrO&_2}fJCHWI7Gl6C07Ii&k#rYBsnI=8)`#`^nP^PC5sfi91q`Mhk~MF_6@mH_ z(hWIN+ec$Jhu{*Pj9-oC;WvqAgSE~PQN};R4FMhwyMtT@15G}b2A=#fG=}&`j3=wG z9_+i4l;U@>Me#v!BkqA^noQmNxlG*&8j7PyGIddy@ib^i;+NAQ=$wbx8+;nm0j_l} zkG3kG1QNuB5#{+N1;84;JR82`|AvdNxm4cHz348-4}!QZs-ddzG&L5g+Ra; zExQ+d5e$l01f-B11c{*OPbb?Y$lRdii(&f#7#a-uBHO*4oV?bZNwzR`blGIvO$cHz z#TRo`nwS+#r7&oiXIeYuc_sO0ka%XlfR5iw;IwP4ehi;l<3QZ%@99w%IXm<6Qfr{i z8Yro&MQtnUoX^XNtwQ9jTfMPdD;K!dkOp@}TWbl@#u|15RBO==DdWl}C~J62DA%Ih zt?Ljh>n=BAqdlxFNv#o{mQpxO{GQqk22xPZzz##GStDW`aR1p`zGu`9%O8er{FdJPL14@0Xc(aq*>6+MZ zEIbV}F?L3ABSQGI;=z#qp|}9M{|OwNj(@=+;CWWD4fmW=Tm_H)qPPUC|E;(f0Ou7q zVEeC%m%wNLQM?S+UQnD0NB*YxW`ymc;@7dHmiQcwNgeU}7=kl{cmzB-llU71^)X@s z;N!%t(aa+5iDowOFf?2Dx{~d5{*N= z3e7^|DVS>{-U?|E@t)k6Qj3Ydgt&zGZjf3^ya-j{Y2tApvy8Y1Nw=JM8N#}PcqgK> zlK6JaJwx0VhOQzu;F;CL%MsQ!#A^|OXNlKf$y(xH0l0zq1?b#Jyd8deh4>9v-9+3w zUB%R^#P6&6~ty z(Y!^RjljH3{1l{jhz~*MyTo&l<=Y7bTB*MfpMae^hzns-Y$x&kIGMYMzefz-Bd!I+ z`=E%Z?Iu2qxethsBjNWDr^2QWiT?zpdx>*l#XjOn@ZC>*4U+34;sc290U$y5LE@v( z@-db``h>U+CjFK8W65;lby)dnj5rTUKZCzO>M&9O{yIY37TI!?I0%1zPCOFQG2;IL zwd2J9z{V5Aje!4(_%hJ`nz$P0`x{sfL%$_1fwI35M}btQaepkS*LW(L=^9@Lz6}}| zz)v$Yr+;B;tRBlA*Z6isbC$;2!E(07=V9a=jjI7YSK|iwVV=h00sVx=7vS6{H4Z`H ze2r&e$pYww!lyJ&gO44J%W#AiYWx#OHfp>B{#&H+To|`l;}yVL0$MO`sm336!~Uny zf_km4(ssg;p*d6lHbw!sBwr>a*fh|!!(C25g|*uWcJ35=84JVRy?P#2hAapdufTj* z=N>yS$v?mb#iI~}T8)#zs8;7pII&LWHsD?x;P!}2U4Z*zus*=;uyT5UH)C(D$tTd% znY9RD;%VA1gm=}nn!~7*InI7g(oc*0MW6Q9w zK80VAu@pWG&o!iQ6O5}xM0cRALwg8qJ=*8sidt|jf}by5Oj2f77?8;iN-?&U0-7I+ z-(Q*7!jn8nw~DcVCwn)xX>Edod5SB=qLvi}JD%!IhO9!79`>YU>no8S83Y(D8n#Y~ z^r$D*P#EW+U9||pNtms?fSypI8 z;a^_rNnMm72>kRe0UH67LSD0`8SL2vu(b(b{}%^y9lp+!e1EU^q?qsU=R7gla(O+! z%cTW71&?1S!mqg$Kc&23Lu%a4jS0}NBtSRCp@&IUyc&mgiEQ$ukOV%4i_V8mnT-3` zvpJkH1(&MVw^ODn-s<%;t!t3N{F*0aTbsp++dL`PI*FsruX|EkYq0q64NvM|t&+Jn zJ?T+_z^*?&^7_boLFNP7QbFPf zQX`W;-O^0rvjh@{6G$A1lW=j4iUj-pg!nv8!aaar{Eozzzaw$1nS>zo@!kM7zr)$c z=D!U@b?`j#T>^=d2_*cdf4>UEe(->N5~mVK{M{$v1?a~(4dlsBJ`Kr}(_TMQ8N!3l zY!^yB!7j|_HaAoHB~Hn$0RQ$h>sD-^)S~mAXm}OiS5FFfh3P+@6ts#_X!(K%W-3Dv z__tvINPizy(`KD|Agye-3>y9mfU;e<_zQ<_Uny|z3kQz9VQ-3w9Dm^m*lx{V(Xib*aA_h)Ckn)ngIW|Q zqL~Zmc|qxmW1Y=i>6vDfZp|p}z0%zmb^Dlvyx3Q3dMH==qGkUg*UnYG81gLZ;fu)` z$fKUIxHmJ9Nxgi*w%syQ;)|IoO&D=0rGQZSY227$zJW4u8F@bFlgO@bBD;Qx?D{ve z6A#6BKq9?A#ObLt0pc6!MFIVP^mQIWrOlv2~vppVIF_ zAv1Xl{m_g%HjbMyZsKUg7Ns^ewzCD9=}I95e(K}hfGZhx41;7P}G>Lc;(cD+1G zJnH*J_hLNF7Y)S5)k7CV&8?{9VXmi#o7GH@tI3#z7a-qOoB_NLiV#EgnJP{8@l5K2 zZJGO_uaxK04A>QMPxAs-MYjfgEdxRB%lAmU!1jU@MTq)foK2^Yw^A@DH5B5O|KzYy!>V`!@))Dh}9Q~0>+R>KK&Imw!a*}6F#69;77 zN?1vkOILHY_Ux3pOK`hdgin(kg-ydeYecPe(b#-Ve$5s9>{VEd-dun zBiMqklKMGdWm8CY^&{C)G*34-V3&M-^>+2cuKB%2Ty`EIqH+>^S@cKzAH_oXF57A! z5{AlsQ(GBM%aZ&{`=Hdf3dzKn_{6QQBokUqZf^NQipC9W<%}+APsPsclGeqy0F=hd z+hWF7X@ik63^A0L^I5k+Z~my(g3)O6L=t#wjOw_P{Fa<&ir z-WfV1()8Ai^Yx}-gZENoQsuoh6>4aon??>CbjS6>Zo6&JEyD&ou^~B4(}w)a&XiF( zP0L2Tsx=M1r$(ocvtx8ryRX#wesn%%IB69fwYk?iy(;pJ$#~EYJLMHotzo1yyCPrP VGRk=cjjC`CqnTgTq*e9|{TJK~su}KH50*=!OgdJol zkwsa?=e%JU&7c8gbq54N!Y-S#IqI8Xo*UzaGXK3*-DBtb(zj0Cd(Z!$|D1boRb9SH zx31HQhT1b@UupaxW$sz|dB0Hue|UKOZ`!pgE1q1vtpDXts+Uru2GKRyLVR9Zh)Knz za-ctXNs2I)hsa>SChlDrbmjS5iu6VSX@SCOc#~7 zo={Rc5k%yl15;C1I_*mpf?m!58y4clMY_3bm2S#DP51Yyg@(*Q78=c8nxk%ZS2uk_ z)J@|E$S3JWNohoArf~?)G}GU?R#Xl(K7^_3eq%U<&{mI!QzHl@ZH>J2>3)5w#3A1Z z_%e=zRMP%IxMQZJ;79Lu5ZBiAdhpS^R1VA)*2MsArWxEEditdIiGz?)zG+DB`~7mtTA3(rqP#Cg26WyEJcA#@uaDQjiJ#vDMN^nt zob|bP*?SPQ|Nneosezxd#W|PTiTXLUn{?^>9e^A9Z~q8h_EHe?3;F_hNQs?^`20lf zG~FHx?|$S|c`@ucx=Gnuf}=db@svWd)t>O`G(78SbopvNE+?+iD8()&rHw%Czo*2Wy~f zWm)HtcWa0{GRMjR9c!rD*U;MRG-#GxRF0%rBULHp7FzwFoi*AW-%3oEx&|&_wst& zX~!c`DurC-yqz~JS^-542=0OIgMtg->3zXAcpnlx2IX;Ba4%RN2)+WWBZ8ZP)=|Op z;PpenL(m+<1e9Qn;4kY6aa{17;CVvuV+il0;5_Jb3Jr#w7W@kYI3u_>@_1J8GbMod zNbphA%{jrJpgAx2D>N4bC&AlA!67u4Fdxmwg4?6{MDPP>E(;!o=8E7wm~d5a4V1Yi zxEh6eUGVn+{8Vr!pAdf+{DpJtj?U4Q82BZ^#J#Tsp8~V51rLVx4Q^ugw}Kl%@SB3~ z2hV>C?hARo6C8zN-wTd_^$%zO@T1^&G5sgOYoXaKI775Q3r>TOzX&dem)qDqFr=LL z2NYTb@n51y&OG9kP;x$TmMz2r;x=d&5)VeRhaJBa6@d5yR`GV(g{W~jH5xHf$6B7PlC zcN6=d-X7v(Vo7hI6?-0L-0N*9P0`~6_Uq_t# zi3<=?^Z@arSeOTin9?V#%@qtB^ z{{$9GHLipt%QUV6{iij)7g{XWxD$M@(0DI~tkk$0e6G^C2jq4%F2-tlM&qx*ceTbx zA?33gKaDuoVB{G9uEjZtK-Xz3Fk`*OK5XFSx=4E;nkFxye6TS}z$K-H+;+_#xU>VU zuEK?N#QCj;6xo6OeU|$tw}wT$6W0i1K6}m4hm-NalHR zXn-#ul8OM=NX-MBg-GTG_#5Q7GQjl^NO_P8F|i`Zo8e|okpF>{%?)w}gscqmD5z9! zb1C{NY_66Qkoo;+E6|=rI|uCr=vR#hXMS1_XIbse?fhI%C#(OEU5DzTr1g{EUl<(rC5z23zrW9ik2I&?vPf6YuUnx zN_3thg*Ln4GKUu)juTmwKxA=@$aAOzUJ@fxBq=YA5mB_3#fT_cPsfP#L2h}u%sq$* zL&9-%#Yn)JN!SM4@S1z!+0|fe0`$5B==Cw^{cy$49aNE7))eXQc{j2GYb^BT4X)Kn z7!rUto|ae>08+sJyG>z5CHp@ofNe=D zc5t@feeQLp^*huzzu{V0QER6Z@lDsn!;$QK%e5L>%cY2KyH*qHB$hb8<63uE=cVzL@d3B5P#97KKDbBf5zNFXmdQu&M|Rx2f0&@h zu>?J8VtQP`g5%>cJ(M~pVtOcbPX0=dQ!cR5mwblbIjw$ep-?*)Fl9kyX6a?wnY)>%G9u}0&7k**=F zHk;HJm1V1)(DGgugsl)`?-#l~O|ne!+#0sZMZhyXP>zGDY7E9eZB>q`o*4~@G>OYL zosb+Jk{cn{qN+HD+KC*pJhytrq~eI+I&lsvw7PK)ic+H(2azTnb7Lxk-}Gq++T=SZ zBLn$&BW<3Dk{?5{Rjs#(qo`spNJMG*Uli$?@1)%lr8gFK7T!g+VM)6n_r^ig$x;YY zei6vk?K)D=_MRWpRwx}j)3Se*C(nJJne3|F(KAyrP)nUWH3J!_rOuvV+iIuj;+dHu zO)}!H)BuFiZ{nB?aPKHJ!Af-L^+}}H*E2nK{Sw(d5ND?p>YvDNK#ZM8lR!L(_DayR zsq%#T4b{XkqazYQf13#UP#jd{b7UM;)%C-1P_^Jk#X#+Wcy{2?3I<1=og{a>M?BM( zf{pQv%&1hX*t4?mbwZ%QJ0}COz|P3czt%kvtQg zqNi}3Dz7PhB+gKi=h5HdxbtJU8B?cE6udxnq0aCw{X6MH&ftYKHw66jo@)APx)q=P zY;3wcM?L|q@pRSQx?Jm-hDeiBc^$otndysh0tNVa>HQM@0B6et>8_b zVTx#)oX4BV#I(#B`0L19sR&OhA+-bjRrv_zNT8QUKBLvbr#!(glYC4H4U&&ERV1HV zLh9K68_DO2P*`p$+ejYtAyuERkUZl;Gvo?*mE=(q^2=>AT1|3)4VB6TxLs|1p{4SA zhuVTdm!+fE6!JVrv3<+9zQ)_*8p z65Xp7TPR;v=RP_M7oq5BSqbkb@k2fE9L?`41x1?Ff)CIG(8bt~{0D2dMs*lb5DM0j z8@PEl{M)Iay>M-1f^%xEjjDd!11z<6F*>wd(~`xOn&n!_mfR;(>r7F7csi zLt(vW8@Qml(pwhQ#vrOs^26t0{KKvKR9k0y$wb_j=-Ns-?jeXxO-PRU0{;c*ke_ht z7GfWyCiCOtPC*Z9L2^vxV%;2pju+&(jR=xj$V<&RQ`kK9ki>1dMcON;WWVDC+h;c{ zkoVe?oHuX@=3}n>80^k%|3a42w|$3vxuakTCGQ8U-?nH!DP6*O`?8VR3@c&!7Vb9 zHgD6-`i~;#lp(3&P9^RnUxHit!CSe(oGB7ACz4&w9luLL8br0;^+78zps4@wzP)=6 z8(uWDr&HAT8uu8~zkj!(-ks_`{Z{M7_#P$GBh?oN9;1w5J^DW~V8)b4N$>FV$c(aZ z&&R{gmcjMx;o+H)p5sd*__~f;rIG5l24@8Gr%rT72{~3{FcOMUsiynD}G&i|kPoVl}H zJx^!O(!v(n50tEcxoQ8Beidi!thJ-mBEx1DwK>AL2T{h2~!qzh46 zTt0eSs6zBY|G`j2#e|CcLzBzGq7?STQ24>J$>KD+qbrIh-C9;LYFtPR%n+h4%weM{ zLZNa|53{ViEbN)oREY7#W#ys>0OKb-G;y*R?e(UJIp~cIO_>-jj)cVu7!RNuerTfD zj{cak@)8h{fBRr+s;ATQNkUM0st_R-V#8eB+`3pd<(#JbttM#5{Jn)n^LNZrgSr~@ z^-_bz0?>=ojk5Bv&`jeMIMYmjLogzj8rxv%y59&QLak;roEisG&|N0GKHaa+mpJ4b z0AI%9X7IH8PK=mw3HZ_b*Q0MeoeduY%H_gbVeJpVx^0TGbP+H2e}eynvG{2&jFVt) zbj}x|jWF7Sr_rS=CX7XJZ4|UdR}1J1)6kfD4H{#dGqhz;yO!*fwrm;c9Sq{0>O5XUd2z|grl@@w$-nD%klX_lKA8VeG{f0E?6RI1IR zr3ich+YG*dS)cS?awjs%mksHCw_k4gvrH7*%KIQ>KDg zYI8f?1ZzB6pi^ z=fb-mIaOW^`#dVkALp4qdxP^-tL(wa63FKbT6O_q_WM0E-X>uAP0viQJEAWAmS+a+ z5aRL2dnVgm9kX?IhhGq$pQ()k&9GlYuKbCfnI)1Xdw&uIz}~!=?G&}{+2$h3%Ixpc z@tcFqy{!HW>8xT@o;ASrBdp8pY-!zUKsnr5x68`Za&0Re3s9!l^A;o| zpA)dP1OE$!@jHGMIqAry@ZX6OFw&WSMj3R0&O&tMR#2vZPhh+ouff<2JRBo8@^Gx_ z&J&!SZH7hqK#}(Z-+<|Q!TIpCNAOixdj(&I^4KT13!->m@OofE%*109+AVjqSe(UJlK!!Wp9dRd5`nyC%3d%Ao5MM^~7;!Ri=Mr~7^EmN9H1mi1k@@it^<4e{-mdy2Rp0$odNK$&&KtB}_9#A}d&zrhQZY#{y)fSZUn z!slk;4n74`N!`exF2tMB-o`ovkMZ6yD-z7eZICtalMo5wO zi2sTUv!3_@a;NZbX* zb%^*d@_U&0CHVe`_#nJ|Ok4)*2=P>ebd-3%taRdMu<}@hxG9_-hh89c0;zyrpAx5` zT22!Cq1P$mVX)2+{}I$aBmNIIo+X|O_;bX~LHj&$1+MSUiBk~h7sMUm>`UToAXRDH z6H8`j{0N$vu)w!kM*OEf+QzBL+eg_KJ* zu0)*6uyPpymuox#fv(VaC-&EBT#qO!b&GMPR=%{Rx>ka9PFmCM1dNs&u)dbobl0-+946I#R4Qxs##JP;xO#_V zqKM3jAu>BkWDYXVbD~7d3~Mu{j|k4K4E$R^K|*#;3|08jAzJAh^mMoAU0 z(>4OnF7%QZ=$aVlrBUeD;EI>ktH>;CobHmy zp*J@|OXQa$G0=5UXeIHMD6}H-N)%d2{Hh0?I9c#(Zp?wiDS}^j`*z|Zg15STJPo0u zd7EoxSR18=+g&RwVx7XJ&O2PQh4lw1;v25j%331l-gK?D)=Fu;mU(v3*vYTi4!p-K8=!4KqsRl zR2oi2NvI3(^lwP~^EV{U{GWs@%Fk{Auz42OMg~W|ydB4bOX8~-5?{xV@Sgv@Q{Z9@ ziEm;^{L3TZCg|HJ4b;hZ9t~M1-@E;^h%h7({$ZEo)B*GY{Nt;Qlzxg*QYXOAu4~=e zEAxBBwG8)Q`o*<;?!k1`wft5)oV)z13uX#K0`RrF;Xh>;PEDI5I(0``+3NH$ymtd( zE4b*Zh;Db1c+po8!&ZrizKZy4m5J!9h~HMF7*8Iit!gpBGXo-7;&OoG{VHW}08ddS zr_ky~4ymym(!5t1J+-trEI8*bd_)RHB&?dJc4Hf7H z+PV=Wq9VC%XzZYu?zsoN)H z#=9vu5iQrW70L~sTg(1N-ga*E%y>7n?w*;DibCoURWlWpbdzV;wmN2ddS;qPmW;TN z56k~-ZCI7uM4qjqXe;_jvQH_)<5c~PrHhTYq%H0WL3RJEA$fxi=EN4uo$ z61T%KX)hYrY>RWcN3#|=a;yi*eM9D9Klb)PR@}Wk4yQX!Z@Q_o9KjZRjMOgzD}$~{ z2Dg&jVJy(iP1q&Bu)4c$#jfc$jmW=*1kp&6_;OK4{GY@^`Rz4x7s`c3dZA`EWIO(z zt=r_|sL#A6gB}x|=_l8XkrP@tD=YM{pwceRu%5Zp&Y9FRyW4F5rN7Fz)41>%v)N}z zCykZD#DzaFo2mHcNc_0)95(#@Rvka|j$TrpCOk$47}{7kIeV9yANne{?u z-O<7ys$-{`V1J|_2jI8+|4YdNS!uY2Rp{p740n(3Un&vH5x zaora~f8xwxeFxuP6dD!&?f2`_P+3W7xXv6|r`7FvAgpWqGo3CaEsgX-d5t~v L_`34a#Q6UKzmu{b