ptrveloc.c revision 4642e01f
14642e01fSmrg/* 24642e01fSmrg * 34642e01fSmrg * Copyright © 2006-2008 Simon Thum simon dot thum at gmx dot de 44642e01fSmrg * 54642e01fSmrg * Permission is hereby granted, free of charge, to any person obtaining a 64642e01fSmrg * copy of this software and associated documentation files (the "Software"), 74642e01fSmrg * to deal in the Software without restriction, including without limitation 84642e01fSmrg * the rights to use, copy, modify, merge, publish, distribute, sublicense, 94642e01fSmrg * and/or sell copies of the Software, and to permit persons to whom the 104642e01fSmrg * Software is furnished to do so, subject to the following conditions: 114642e01fSmrg * 124642e01fSmrg * The above copyright notice and this permission notice (including the next 134642e01fSmrg * paragraph) shall be included in all copies or substantial portions of the 144642e01fSmrg * Software. 154642e01fSmrg * 164642e01fSmrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 174642e01fSmrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 184642e01fSmrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 194642e01fSmrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 204642e01fSmrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 214642e01fSmrg * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 224642e01fSmrg * DEALINGS IN THE SOFTWARE. 234642e01fSmrg */ 244642e01fSmrg 254642e01fSmrg#ifdef HAVE_DIX_CONFIG_H 264642e01fSmrg#include <dix-config.h> 274642e01fSmrg#endif 284642e01fSmrg 294642e01fSmrg#include <math.h> 304642e01fSmrg#include <ptrveloc.h> 314642e01fSmrg#include <inputstr.h> 324642e01fSmrg#include <assert.h> 334642e01fSmrg#include <os.h> 344642e01fSmrg 354642e01fSmrg/***************************************************************************** 364642e01fSmrg * Predictable pointer acceleration 374642e01fSmrg * 384642e01fSmrg * 2006-2008 by Simon Thum (simon [dot] thum [at] gmx de) 394642e01fSmrg * 404642e01fSmrg * Serves 3 complementary functions: 414642e01fSmrg * 1) provide a sophisticated ballistic velocity estimate to improve 424642e01fSmrg * the relation between velocity (of the device) and acceleration 434642e01fSmrg * 2) make arbitrary acceleration profiles possible 444642e01fSmrg * 3) decelerate by two means (constant and adaptive) if enabled 454642e01fSmrg * 464642e01fSmrg * Important concepts are the 474642e01fSmrg * 484642e01fSmrg * - Scheme 494642e01fSmrg * which selects the basic algorithm 504642e01fSmrg * (see devices.c/InitPointerAccelerationScheme) 514642e01fSmrg * - Profile 524642e01fSmrg * which returns an acceleration 534642e01fSmrg * for a given velocity 544642e01fSmrg * 554642e01fSmrg * The profile can be selected by the user (potentially at runtime). 564642e01fSmrg * the classic profile is intended to cleanly perform old-style 574642e01fSmrg * function selection (threshold =/!= 0) 584642e01fSmrg * 594642e01fSmrg ****************************************************************************/ 604642e01fSmrg 614642e01fSmrg/* fwds */ 624642e01fSmrgstatic inline void 634642e01fSmrgFeedFilterStage(FilterStagePtr s, float value, int tdiff); 644642e01fSmrgextern void 654642e01fSmrgInitFilterStage(FilterStagePtr s, float rdecay, int lutsize); 664642e01fSmrgvoid 674642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s); 684642e01fSmrgint 694642e01fSmrgSetAccelerationProfile(DeviceVelocityPtr s, int profile_num); 704642e01fSmrgvoid 714642e01fSmrgInitFilterChain(DeviceVelocityPtr s, float rdecay, float degression, 724642e01fSmrg int stages, int lutsize); 734642e01fSmrgvoid 744642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s); 754642e01fSmrgstatic float 764642e01fSmrgSimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity, 774642e01fSmrg float threshold, float acc); 784642e01fSmrg 794642e01fSmrg 804642e01fSmrg 814642e01fSmrg/*#define PTRACCEL_DEBUGGING*/ 824642e01fSmrg 834642e01fSmrg#ifdef PTRACCEL_DEBUGGING 844642e01fSmrg#define DebugAccelF ErrorF 854642e01fSmrg#else 864642e01fSmrg#define DebugAccelF(...) /* */ 874642e01fSmrg#endif 884642e01fSmrg 894642e01fSmrg/******************************** 904642e01fSmrg * Init/Uninit etc 914642e01fSmrg *******************************/ 924642e01fSmrg 934642e01fSmrg/** 944642e01fSmrg * Init struct so it should match the average case 954642e01fSmrg */ 964642e01fSmrgvoid 974642e01fSmrgInitVelocityData(DeviceVelocityPtr s) 984642e01fSmrg{ 994642e01fSmrg memset(s, 0, sizeof(DeviceVelocityRec)); 1004642e01fSmrg 1014642e01fSmrg s->corr_mul = 10.0; /* dots per 10 milisecond should be usable */ 1024642e01fSmrg s->const_acceleration = 1.0; /* no acceleration/deceleration */ 1034642e01fSmrg s->reset_time = 300; 1044642e01fSmrg s->use_softening = 1; 1054642e01fSmrg s->min_acceleration = 1.0; /* don't decelerate */ 1064642e01fSmrg s->coupling = 0.25; 1074642e01fSmrg s->average_accel = TRUE; 1084642e01fSmrg SetAccelerationProfile(s, AccelProfileClassic); 1094642e01fSmrg InitFilterChain(s, (float)1.0/20.0, 1, 1, 40); 1104642e01fSmrg} 1114642e01fSmrg 1124642e01fSmrg 1134642e01fSmrg/** 1144642e01fSmrg * Clean up 1154642e01fSmrg */ 1164642e01fSmrgstatic void 1174642e01fSmrgFreeVelocityData(DeviceVelocityPtr s){ 1184642e01fSmrg CleanupFilterChain(s); 1194642e01fSmrg SetAccelerationProfile(s, -1); 1204642e01fSmrg} 1214642e01fSmrg 1224642e01fSmrg 1234642e01fSmrg/* 1244642e01fSmrg * dix uninit helper, called through scheme 1254642e01fSmrg */ 1264642e01fSmrgvoid 1274642e01fSmrgAccelerationDefaultCleanup(DeviceIntPtr pDev) 1284642e01fSmrg{ 1294642e01fSmrg /*sanity check*/ 1304642e01fSmrg if( pDev->valuator->accelScheme.AccelSchemeProc == acceleratePointerPredictable 1314642e01fSmrg && pDev->valuator->accelScheme.accelData != NULL){ 1324642e01fSmrg pDev->valuator->accelScheme.AccelSchemeProc = NULL; 1334642e01fSmrg FreeVelocityData(pDev->valuator->accelScheme.accelData); 1344642e01fSmrg xfree(pDev->valuator->accelScheme.accelData); 1354642e01fSmrg pDev->valuator->accelScheme.accelData = NULL; 1364642e01fSmrg } 1374642e01fSmrg} 1384642e01fSmrg 1394642e01fSmrg/********************* 1404642e01fSmrg * Filtering logic 1414642e01fSmrg ********************/ 1424642e01fSmrg 1434642e01fSmrg/** 1444642e01fSmrgInitialize a filter chain. 1454642e01fSmrgExpected result is a series of filters, each progressively more integrating. 1464642e01fSmrg 1474642e01fSmrgThis allows for two strategies: Either you have one filter which is reasonable 1484642e01fSmrgand is being coupled to account for fast-changing input, or you have 'one for 1494642e01fSmrgevery situation'. You might want to have tighter coupling then, e.g. 0.1. 1504642e01fSmrgIn the filter stats, you can see if a reasonable filter useage emerges. 1514642e01fSmrg*/ 1524642e01fSmrgvoid 1534642e01fSmrgInitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize) 1544642e01fSmrg{ 1554642e01fSmrg int fn; 1564642e01fSmrg if((stages > 1 && progression < 1.0f) || 0 == progression){ 1574642e01fSmrg ErrorF("(dix ptracc) invalid filter chain progression specified\n"); 1584642e01fSmrg return; 1594642e01fSmrg } 1604642e01fSmrg /* Block here to support runtime filter adjustment */ 1614642e01fSmrg OsBlockSignals(); 1624642e01fSmrg for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ 1634642e01fSmrg if(fn < stages){ 1644642e01fSmrg InitFilterStage(&s->filters[fn], rdecay, lutsize); 1654642e01fSmrg }else{ 1664642e01fSmrg InitFilterStage(&s->filters[fn], 0, 0); 1674642e01fSmrg } 1684642e01fSmrg rdecay /= progression; 1694642e01fSmrg } 1704642e01fSmrg /* release again. Should the input loop be threaded, we also need 1714642e01fSmrg * memory release here (in principle). 1724642e01fSmrg */ 1734642e01fSmrg OsReleaseSignals(); 1744642e01fSmrg} 1754642e01fSmrg 1764642e01fSmrg 1774642e01fSmrgvoid 1784642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s) 1794642e01fSmrg{ 1804642e01fSmrg int fn; 1814642e01fSmrg 1824642e01fSmrg for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++) 1834642e01fSmrg InitFilterStage(&s->filters[fn], 0, 0); 1844642e01fSmrg} 1854642e01fSmrg 1864642e01fSmrgstatic inline void 1874642e01fSmrgStuffFilterChain(DeviceVelocityPtr s, float value) 1884642e01fSmrg{ 1894642e01fSmrg int fn; 1904642e01fSmrg 1914642e01fSmrg for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ 1924642e01fSmrg if(s->filters[fn].rdecay != 0) 1934642e01fSmrg s->filters[fn].current = value; 1944642e01fSmrg else break; 1954642e01fSmrg } 1964642e01fSmrg} 1974642e01fSmrg 1984642e01fSmrg 1994642e01fSmrg/** 2004642e01fSmrg * Adjust weighting decay and lut for a stage 2014642e01fSmrg * The weight fn is designed so its integral 0->inf is unity, so we end 2024642e01fSmrg * up with a stable (basically IIR) filter. It always draws 2034642e01fSmrg * towards its more current input values, which have more weight the older 2044642e01fSmrg * the last input value is. 2054642e01fSmrg */ 2064642e01fSmrgvoid 2074642e01fSmrgInitFilterStage(FilterStagePtr s, float rdecay, int lutsize) 2084642e01fSmrg{ 2094642e01fSmrg int x; 2104642e01fSmrg float *newlut; 2114642e01fSmrg float *oldlut; 2124642e01fSmrg 2134642e01fSmrg s->fading_lut_size = 0; /* prevent access */ 2144642e01fSmrg 2154642e01fSmrg if(lutsize > 0){ 2164642e01fSmrg newlut = xalloc (sizeof(float)* lutsize); 2174642e01fSmrg if(!newlut) 2184642e01fSmrg return; 2194642e01fSmrg for(x = 0; x < lutsize; x++) 2204642e01fSmrg newlut[x] = pow(0.5, ((float)x) * rdecay); 2214642e01fSmrg }else{ 2224642e01fSmrg newlut = NULL; 2234642e01fSmrg } 2244642e01fSmrg oldlut = s->fading_lut; 2254642e01fSmrg s->fading_lut = newlut; 2264642e01fSmrg s->rdecay = rdecay; 2274642e01fSmrg s->fading_lut_size = lutsize; 2284642e01fSmrg s->current = 0; 2294642e01fSmrg if(oldlut != NULL) 2304642e01fSmrg xfree(oldlut); 2314642e01fSmrg} 2324642e01fSmrg 2334642e01fSmrg 2344642e01fSmrgstatic inline void 2354642e01fSmrgFeedFilterChain(DeviceVelocityPtr s, float value, int tdiff) 2364642e01fSmrg{ 2374642e01fSmrg int fn; 2384642e01fSmrg 2394642e01fSmrg for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ 2404642e01fSmrg if(s->filters[fn].rdecay != 0) 2414642e01fSmrg FeedFilterStage(&s->filters[fn], value, tdiff); 2424642e01fSmrg else break; 2434642e01fSmrg } 2444642e01fSmrg} 2454642e01fSmrg 2464642e01fSmrg 2474642e01fSmrgstatic inline void 2484642e01fSmrgFeedFilterStage(FilterStagePtr s, float value, int tdiff){ 2494642e01fSmrg float fade; 2504642e01fSmrg if(tdiff < s->fading_lut_size) 2514642e01fSmrg fade = s->fading_lut[tdiff]; 2524642e01fSmrg else 2534642e01fSmrg fade = pow(0.5, ((float)tdiff) * s->rdecay); 2544642e01fSmrg s->current *= fade; /* fade out old velocity */ 2554642e01fSmrg s->current += value * (1.0f - fade); /* and add up current */ 2564642e01fSmrg} 2574642e01fSmrg 2584642e01fSmrg/** 2594642e01fSmrg * Select the most filtered matching result. Also, the first 2604642e01fSmrg * mismatching filter may be set to value (coupling). 2614642e01fSmrg */ 2624642e01fSmrgstatic inline float 2634642e01fSmrgQueryFilterChain( 2644642e01fSmrg DeviceVelocityPtr s, 2654642e01fSmrg float value) 2664642e01fSmrg{ 2674642e01fSmrg int fn, rfn = 0, cfn = -1; 2684642e01fSmrg float cur, result = value; 2694642e01fSmrg 2704642e01fSmrg /* try to retrieve most integrated result 'within range' 2714642e01fSmrg * Assumption: filter are in order least to most integrating */ 2724642e01fSmrg for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ 2734642e01fSmrg if(0.0f == s->filters[fn].rdecay) 2744642e01fSmrg break; 2754642e01fSmrg cur = s->filters[fn].current; 2764642e01fSmrg 2774642e01fSmrg if (fabs(value - cur) <= (s->coupling * (value + cur))){ 2784642e01fSmrg result = cur; 2794642e01fSmrg rfn = fn + 1; /*remember result determining filter */ 2804642e01fSmrg } else if(cfn == -1){ 2814642e01fSmrg cfn = fn; /* remember first mismatching filter */ 2824642e01fSmrg } 2834642e01fSmrg } 2844642e01fSmrg 2854642e01fSmrg s->statistics.filter_usecount[rfn]++; 2864642e01fSmrg DebugAccelF("(dix ptracc) result from stage %i, input %.2f, output %.2f\n", 2874642e01fSmrg rfn, value, result); 2884642e01fSmrg 2894642e01fSmrg /* override first mismatching current (coupling) so the filter 2904642e01fSmrg * catches up quickly. */ 2914642e01fSmrg if(cfn != -1) 2924642e01fSmrg s->filters[cfn].current = result; 2934642e01fSmrg 2944642e01fSmrg return result; 2954642e01fSmrg} 2964642e01fSmrg 2974642e01fSmrg/******************************** 2984642e01fSmrg * velocity computation 2994642e01fSmrg *******************************/ 3004642e01fSmrg 3014642e01fSmrg/** 3024642e01fSmrg * return the axis if mickey is insignificant and axis-aligned, 3034642e01fSmrg * -1 otherwise 3044642e01fSmrg * 1 for x-axis 3054642e01fSmrg * 2 for y-axis 3064642e01fSmrg */ 3074642e01fSmrgstatic inline short 3084642e01fSmrgGetAxis(int dx, int dy){ 3094642e01fSmrg if(dx == 0 || dy == 0){ 3104642e01fSmrg if(dx == 1 || dx == -1) 3114642e01fSmrg return 1; 3124642e01fSmrg if(dy == 1 || dy == -1) 3134642e01fSmrg return 2; 3144642e01fSmrg return -1; 3154642e01fSmrg }else{ 3164642e01fSmrg return -1; 3174642e01fSmrg } 3184642e01fSmrg} 3194642e01fSmrg 3204642e01fSmrg 3214642e01fSmrg/** 3224642e01fSmrg * Perform velocity approximation 3234642e01fSmrg * return true if non-visible state reset is suggested 3244642e01fSmrg */ 3254642e01fSmrgstatic short 3264642e01fSmrgProcessVelocityData( 3274642e01fSmrg DeviceVelocityPtr s, 3284642e01fSmrg int dx, 3294642e01fSmrg int dy, 3304642e01fSmrg int time) 3314642e01fSmrg{ 3324642e01fSmrg float cvelocity; 3334642e01fSmrg 3344642e01fSmrg int diff = time - s->lrm_time; 3354642e01fSmrg int cur_ax, last_ax; 3364642e01fSmrg short reset = (diff >= s->reset_time); 3374642e01fSmrg 3384642e01fSmrg /* remember last round's result */ 3394642e01fSmrg s->last_velocity = s->velocity; 3404642e01fSmrg cur_ax = GetAxis(dx, dy); 3414642e01fSmrg last_ax = GetAxis(s->last_dx, s->last_dy); 3424642e01fSmrg 3434642e01fSmrg if(cur_ax != last_ax && cur_ax != -1 && last_ax != -1 && !reset){ 3444642e01fSmrg /* correct for the error induced when diagonal movements are 3454642e01fSmrg reported as alternating axis mickeys */ 3464642e01fSmrg dx += s->last_dx; 3474642e01fSmrg dy += s->last_dy; 3484642e01fSmrg diff += s->last_diff; 3494642e01fSmrg s->last_diff = time - s->lrm_time; /* prevent repeating add-up */ 3504642e01fSmrg DebugAccelF("(dix ptracc) axial correction\n"); 3514642e01fSmrg }else{ 3524642e01fSmrg s->last_diff = diff; 3534642e01fSmrg } 3544642e01fSmrg 3554642e01fSmrg /* 3564642e01fSmrg * cvelocity is not a real velocity yet, more a motion delta. constant 3574642e01fSmrg * acceleration is multiplied here to make the velocity an on-screen 3584642e01fSmrg * velocity (pix/t as opposed to [insert unit]/t). This is intended to 3594642e01fSmrg * make multiple devices with widely varying ConstantDecelerations respond 3604642e01fSmrg * similar to acceleration controls. 3614642e01fSmrg */ 3624642e01fSmrg cvelocity = (float)sqrt(dx*dx + dy*dy) * s->const_acceleration; 3634642e01fSmrg 3644642e01fSmrg s->lrm_time = time; 3654642e01fSmrg 3664642e01fSmrg if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */ 3674642e01fSmrg /* simply set velocity from current movement, no reset. */ 3684642e01fSmrg s->velocity = cvelocity; 3694642e01fSmrg return FALSE; 3704642e01fSmrg } 3714642e01fSmrg 3724642e01fSmrg if (diff == 0) 3734642e01fSmrg diff = 1; /* prevent div-by-zero, though it shouldn't happen anyway*/ 3744642e01fSmrg 3754642e01fSmrg /* translate velocity to dots/ms (somewhat intractable in integers, 3764642e01fSmrg so we multiply by some per-device adjustable factor) */ 3774642e01fSmrg cvelocity = cvelocity * s->corr_mul / (float)diff; 3784642e01fSmrg 3794642e01fSmrg /* short-circuit: when nv-reset the rest can be skipped */ 3804642e01fSmrg if(reset == TRUE){ 3814642e01fSmrg /* 3824642e01fSmrg * we don't really have a velocity here, since diff includes inactive 3834642e01fSmrg * time. This is dealt with in ComputeAcceleration. 3844642e01fSmrg */ 3854642e01fSmrg StuffFilterChain(s, cvelocity); 3864642e01fSmrg s->velocity = s->last_velocity = cvelocity; 3874642e01fSmrg s->last_reset = TRUE; 3884642e01fSmrg DebugAccelF("(dix ptracc) non-visible state reset\n"); 3894642e01fSmrg return TRUE; 3904642e01fSmrg } 3914642e01fSmrg 3924642e01fSmrg if(s->last_reset == TRUE){ 3934642e01fSmrg /* 3944642e01fSmrg * when here, we're probably processing the second mickey of a starting 3954642e01fSmrg * stroke. This happens to be the first time we can reasonably pretend 3964642e01fSmrg * that cvelocity is an actual velocity. Thus, to opt precision, we 3974642e01fSmrg * stuff that into the filter chain. 3984642e01fSmrg */ 3994642e01fSmrg s->last_reset = FALSE; 4004642e01fSmrg DebugAccelF("(dix ptracc) after-reset vel:%.3f\n", cvelocity); 4014642e01fSmrg StuffFilterChain(s, cvelocity); 4024642e01fSmrg s->velocity = cvelocity; 4034642e01fSmrg return FALSE; 4044642e01fSmrg } 4054642e01fSmrg 4064642e01fSmrg /* feed into filter chain */ 4074642e01fSmrg FeedFilterChain(s, cvelocity, diff); 4084642e01fSmrg 4094642e01fSmrg /* perform coupling and decide final value */ 4104642e01fSmrg s->velocity = QueryFilterChain(s, cvelocity); 4114642e01fSmrg 4124642e01fSmrg DebugAccelF("(dix ptracc) guess: vel=%.3f diff=%d %i|%i|%i|%i|%i|%i|%i|%i|%i\n", 4134642e01fSmrg s->velocity, diff, 4144642e01fSmrg s->statistics.filter_usecount[0], s->statistics.filter_usecount[1], 4154642e01fSmrg s->statistics.filter_usecount[2], s->statistics.filter_usecount[3], 4164642e01fSmrg s->statistics.filter_usecount[4], s->statistics.filter_usecount[5], 4174642e01fSmrg s->statistics.filter_usecount[6], s->statistics.filter_usecount[7], 4184642e01fSmrg s->statistics.filter_usecount[8]); 4194642e01fSmrg return FALSE; 4204642e01fSmrg} 4214642e01fSmrg 4224642e01fSmrg 4234642e01fSmrg/** 4244642e01fSmrg * this flattens significant ( > 1) mickeys a little bit for more steady 4254642e01fSmrg * constant-velocity response 4264642e01fSmrg */ 4274642e01fSmrgstatic inline float 4284642e01fSmrgApplySimpleSoftening(int od, int d) 4294642e01fSmrg{ 4304642e01fSmrg float res = d; 4314642e01fSmrg if (d <= 1 && d >= -1) 4324642e01fSmrg return res; 4334642e01fSmrg if (d > od) 4344642e01fSmrg res -= 0.5; 4354642e01fSmrg else if (d < od) 4364642e01fSmrg res += 0.5; 4374642e01fSmrg return res; 4384642e01fSmrg} 4394642e01fSmrg 4404642e01fSmrg 4414642e01fSmrgstatic void 4424642e01fSmrgApplySofteningAndConstantDeceleration( 4434642e01fSmrg DeviceVelocityPtr s, 4444642e01fSmrg int dx, 4454642e01fSmrg int dy, 4464642e01fSmrg float* fdx, 4474642e01fSmrg float* fdy, 4484642e01fSmrg short do_soften) 4494642e01fSmrg{ 4504642e01fSmrg if (do_soften && s->use_softening) { 4514642e01fSmrg *fdx = ApplySimpleSoftening(s->last_dx, dx); 4524642e01fSmrg *fdy = ApplySimpleSoftening(s->last_dy, dy); 4534642e01fSmrg } else { 4544642e01fSmrg *fdx = dx; 4554642e01fSmrg *fdy = dy; 4564642e01fSmrg } 4574642e01fSmrg 4584642e01fSmrg *fdx *= s->const_acceleration; 4594642e01fSmrg *fdy *= s->const_acceleration; 4604642e01fSmrg} 4614642e01fSmrg 4624642e01fSmrg/* 4634642e01fSmrg * compute the acceleration for given velocity and enforce min_acceleartion 4644642e01fSmrg */ 4654642e01fSmrgstatic float 4664642e01fSmrgBasicComputeAcceleration( 4674642e01fSmrg DeviceVelocityPtr pVel, 4684642e01fSmrg float velocity, 4694642e01fSmrg float threshold, 4704642e01fSmrg float acc){ 4714642e01fSmrg 4724642e01fSmrg float result; 4734642e01fSmrg result = pVel->Profile(pVel, velocity, threshold, acc); 4744642e01fSmrg 4754642e01fSmrg /* enforce min_acceleration */ 4764642e01fSmrg if (result < pVel->min_acceleration) 4774642e01fSmrg result = pVel->min_acceleration; 4784642e01fSmrg return result; 4794642e01fSmrg} 4804642e01fSmrg 4814642e01fSmrg/** 4824642e01fSmrg * Compute acceleration. Takes into account averaging, nv-reset, etc. 4834642e01fSmrg */ 4844642e01fSmrgstatic float 4854642e01fSmrgComputeAcceleration( 4864642e01fSmrg DeviceVelocityPtr vel, 4874642e01fSmrg float threshold, 4884642e01fSmrg float acc){ 4894642e01fSmrg float res; 4904642e01fSmrg 4914642e01fSmrg if(vel->last_reset){ 4924642e01fSmrg DebugAccelF("(dix ptracc) profile skipped\n"); 4934642e01fSmrg /* 4944642e01fSmrg * This is intended to override the first estimate of a stroke, 4954642e01fSmrg * which is too low (see ProcessVelocityData). 1 should make sure 4964642e01fSmrg * the mickey is seen on screen. 4974642e01fSmrg */ 4984642e01fSmrg return 1; 4994642e01fSmrg } 5004642e01fSmrg 5014642e01fSmrg if(vel->average_accel && vel->velocity != vel->last_velocity){ 5024642e01fSmrg /* use simpson's rule to average acceleration between 5034642e01fSmrg * current and previous velocity. 5044642e01fSmrg * Though being the more natural choice, it causes a minor delay 5054642e01fSmrg * in comparison, so it can be disabled. */ 5064642e01fSmrg res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc); 5074642e01fSmrg res += BasicComputeAcceleration(vel, vel->last_velocity, threshold, acc); 5084642e01fSmrg res += 4.0f * BasicComputeAcceleration(vel, 5094642e01fSmrg (vel->last_velocity + vel->velocity) / 2, 5104642e01fSmrg threshold, acc); 5114642e01fSmrg res /= 6.0f; 5124642e01fSmrg DebugAccelF("(dix ptracc) profile average [%.2f ... %.2f] is %.3f\n", 5134642e01fSmrg vel->velocity, vel->last_velocity, res); 5144642e01fSmrg return res; 5154642e01fSmrg }else{ 5164642e01fSmrg res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc); 5174642e01fSmrg DebugAccelF("(dix ptracc) profile sample [%.2f] is %.3f\n", 5184642e01fSmrg vel->velocity, res); 5194642e01fSmrg return res; 5204642e01fSmrg } 5214642e01fSmrg} 5224642e01fSmrg 5234642e01fSmrg 5244642e01fSmrg/***************************************** 5254642e01fSmrg * Acceleration functions and profiles 5264642e01fSmrg ****************************************/ 5274642e01fSmrg 5284642e01fSmrg/** 5294642e01fSmrg * Polynomial function similar previous one, but with f(1) = 1 5304642e01fSmrg */ 5314642e01fSmrgstatic float 5324642e01fSmrgPolynomialAccelerationProfile( 5334642e01fSmrg DeviceVelocityPtr pVel, 5344642e01fSmrg float velocity, 5354642e01fSmrg float ignored, 5364642e01fSmrg float acc) 5374642e01fSmrg{ 5384642e01fSmrg return pow(velocity, (acc - 1.0) * 0.5); 5394642e01fSmrg} 5404642e01fSmrg 5414642e01fSmrg 5424642e01fSmrg/** 5434642e01fSmrg * returns acceleration for velocity. 5444642e01fSmrg * This profile selects the two functions like the old scheme did 5454642e01fSmrg */ 5464642e01fSmrgstatic float 5474642e01fSmrgClassicProfile( 5484642e01fSmrg DeviceVelocityPtr pVel, 5494642e01fSmrg float velocity, 5504642e01fSmrg float threshold, 5514642e01fSmrg float acc) 5524642e01fSmrg{ 5534642e01fSmrg if (threshold) { 5544642e01fSmrg return SimpleSmoothProfile (pVel, 5554642e01fSmrg velocity, 5564642e01fSmrg threshold, 5574642e01fSmrg acc); 5584642e01fSmrg } else { 5594642e01fSmrg return PolynomialAccelerationProfile (pVel, 5604642e01fSmrg velocity, 5614642e01fSmrg 0, 5624642e01fSmrg acc); 5634642e01fSmrg } 5644642e01fSmrg} 5654642e01fSmrg 5664642e01fSmrg 5674642e01fSmrg/** 5684642e01fSmrg * Power profile 5694642e01fSmrg * This has a completely smooth transition curve, i.e. no jumps in the 5704642e01fSmrg * derivatives. 5714642e01fSmrg * 5724642e01fSmrg * This has the expense of overall response dependency on min-acceleration. 5734642e01fSmrg * In effect, min_acceleration mimics const_acceleration in this profile. 5744642e01fSmrg */ 5754642e01fSmrgstatic float 5764642e01fSmrgPowerProfile( 5774642e01fSmrg DeviceVelocityPtr pVel, 5784642e01fSmrg float velocity, 5794642e01fSmrg float threshold, 5804642e01fSmrg float acc) 5814642e01fSmrg{ 5824642e01fSmrg float vel_dist; 5834642e01fSmrg 5844642e01fSmrg acc = (acc-1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */ 5854642e01fSmrg 5864642e01fSmrg if (velocity <= threshold) 5874642e01fSmrg return pVel->min_acceleration; 5884642e01fSmrg vel_dist = velocity - threshold; 5894642e01fSmrg return (pow(acc, vel_dist)) * pVel->min_acceleration; 5904642e01fSmrg} 5914642e01fSmrg 5924642e01fSmrg 5934642e01fSmrg/** 5944642e01fSmrg * just a smooth function in [0..1] -> [0..1] 5954642e01fSmrg * - point symmetry at 0.5 5964642e01fSmrg * - f'(0) = f'(1) = 0 5974642e01fSmrg * - starts faster than a sinoid 5984642e01fSmrg * - smoothness C1 (Cinf if you dare to ignore endpoints) 5994642e01fSmrg */ 6004642e01fSmrgstatic inline float 6014642e01fSmrgCalcPenumbralGradient(float x){ 6024642e01fSmrg x *= 2.0f; 6034642e01fSmrg x -= 1.0f; 6044642e01fSmrg return 0.5f + (x * sqrt(1.0f - x*x) + asin(x))/M_PI; 6054642e01fSmrg} 6064642e01fSmrg 6074642e01fSmrg 6084642e01fSmrg/** 6094642e01fSmrg * acceleration function similar to classic accelerated/unaccelerated, 6104642e01fSmrg * but with smooth transition in between (and towards zero for adaptive dec.). 6114642e01fSmrg */ 6124642e01fSmrgstatic float 6134642e01fSmrgSimpleSmoothProfile( 6144642e01fSmrg DeviceVelocityPtr pVel, 6154642e01fSmrg float velocity, 6164642e01fSmrg float threshold, 6174642e01fSmrg float acc) 6184642e01fSmrg{ 6194642e01fSmrg if(velocity < 1.0f) 6204642e01fSmrg return CalcPenumbralGradient(0.5 + velocity*0.5) * 2.0f - 1.0f; 6214642e01fSmrg if(threshold < 1.0f) 6224642e01fSmrg threshold = 1.0f; 6234642e01fSmrg if (velocity <= threshold) 6244642e01fSmrg return 1; 6254642e01fSmrg velocity /= threshold; 6264642e01fSmrg if (velocity >= acc) 6274642e01fSmrg return acc; 6284642e01fSmrg else 6294642e01fSmrg return 1.0f + (CalcPenumbralGradient(velocity/acc) * (acc - 1.0f)); 6304642e01fSmrg} 6314642e01fSmrg 6324642e01fSmrg 6334642e01fSmrg/** 6344642e01fSmrg * This profile uses the first half of the penumbral gradient as a start 6354642e01fSmrg * and then scales linearly. 6364642e01fSmrg */ 6374642e01fSmrgstatic float 6384642e01fSmrgSmoothLinearProfile( 6394642e01fSmrg DeviceVelocityPtr pVel, 6404642e01fSmrg float velocity, 6414642e01fSmrg float threshold, 6424642e01fSmrg float acc) 6434642e01fSmrg{ 6444642e01fSmrg float res, nv; 6454642e01fSmrg 6464642e01fSmrg if(acc > 1.0f) 6474642e01fSmrg acc -= 1.0f; /*this is so acc = 1 is no acceleration */ 6484642e01fSmrg else 6494642e01fSmrg return 1.0f; 6504642e01fSmrg 6514642e01fSmrg nv = (velocity - threshold) * acc * 0.5f; 6524642e01fSmrg 6534642e01fSmrg if(nv < 0){ 6544642e01fSmrg res = 0; 6554642e01fSmrg }else if(nv < 2){ 6564642e01fSmrg res = CalcPenumbralGradient(nv*0.25f)*2.0f; 6574642e01fSmrg }else{ 6584642e01fSmrg nv -= 2.0f; 6594642e01fSmrg res = nv * 2.0f / M_PI /* steepness of gradient at 0.5 */ 6604642e01fSmrg + 1.0f; /* gradient crosses 2|1 */ 6614642e01fSmrg } 6624642e01fSmrg res += pVel->min_acceleration; 6634642e01fSmrg return res; 6644642e01fSmrg} 6654642e01fSmrg 6664642e01fSmrg 6674642e01fSmrgstatic float 6684642e01fSmrgLinearProfile( 6694642e01fSmrg DeviceVelocityPtr pVel, 6704642e01fSmrg float velocity, 6714642e01fSmrg float threshold, 6724642e01fSmrg float acc) 6734642e01fSmrg{ 6744642e01fSmrg return acc * velocity; 6754642e01fSmrg} 6764642e01fSmrg 6774642e01fSmrg 6784642e01fSmrg/** 6794642e01fSmrg * Set the profile by number. 6804642e01fSmrg * Intended to make profiles exchangeable at runtime. 6814642e01fSmrg * If you created a profile, give it a number here and in the header to 6824642e01fSmrg * make it selectable. In case some profile-specific init is needed, here 6834642e01fSmrg * would be a good place, since FreeVelocityData() also calls this with -1. 6844642e01fSmrg * returns FALSE (0) if profile number is unavailable. 6854642e01fSmrg */ 6864642e01fSmrg_X_EXPORT int 6874642e01fSmrgSetAccelerationProfile( 6884642e01fSmrg DeviceVelocityPtr s, 6894642e01fSmrg int profile_num) 6904642e01fSmrg{ 6914642e01fSmrg PointerAccelerationProfileFunc profile; 6924642e01fSmrg switch(profile_num){ 6934642e01fSmrg case -1: 6944642e01fSmrg profile = NULL; /* Special case to uninit properly */ 6954642e01fSmrg break; 6964642e01fSmrg case AccelProfileClassic: 6974642e01fSmrg profile = ClassicProfile; 6984642e01fSmrg break; 6994642e01fSmrg case AccelProfileDeviceSpecific: 7004642e01fSmrg if(NULL == s->deviceSpecificProfile) 7014642e01fSmrg return FALSE; 7024642e01fSmrg profile = s->deviceSpecificProfile; 7034642e01fSmrg break; 7044642e01fSmrg case AccelProfilePolynomial: 7054642e01fSmrg profile = PolynomialAccelerationProfile; 7064642e01fSmrg break; 7074642e01fSmrg case AccelProfileSmoothLinear: 7084642e01fSmrg profile = SmoothLinearProfile; 7094642e01fSmrg break; 7104642e01fSmrg case AccelProfileSimple: 7114642e01fSmrg profile = SimpleSmoothProfile; 7124642e01fSmrg break; 7134642e01fSmrg case AccelProfilePower: 7144642e01fSmrg profile = PowerProfile; 7154642e01fSmrg break; 7164642e01fSmrg case AccelProfileLinear: 7174642e01fSmrg profile = LinearProfile; 7184642e01fSmrg break; 7194642e01fSmrg case AccelProfileReserved: 7204642e01fSmrg /* reserved for future use, e.g. a user-defined profile */ 7214642e01fSmrg default: 7224642e01fSmrg return FALSE; 7234642e01fSmrg } 7244642e01fSmrg if(s->profile_private != NULL){ 7254642e01fSmrg /* Here one could free old profile-private data */ 7264642e01fSmrg xfree(s->profile_private); 7274642e01fSmrg s->profile_private = NULL; 7284642e01fSmrg } 7294642e01fSmrg /* Here one could init profile-private data */ 7304642e01fSmrg s->Profile = profile; 7314642e01fSmrg s->statistics.profile_number = profile_num; 7324642e01fSmrg return TRUE; 7334642e01fSmrg} 7344642e01fSmrg 7354642e01fSmrg/********************************************** 7364642e01fSmrg * driver interaction 7374642e01fSmrg **********************************************/ 7384642e01fSmrg 7394642e01fSmrg 7404642e01fSmrg/** 7414642e01fSmrg * device-specific profile 7424642e01fSmrg * 7434642e01fSmrg * The device-specific profile is intended as a hook for a driver 7444642e01fSmrg * which may want to provide an own acceleration profile. 7454642e01fSmrg * It should not rely on profile-private data, instead 7464642e01fSmrg * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends). 7474642e01fSmrg * Users may override or choose it. 7484642e01fSmrg */ 7494642e01fSmrg_X_EXPORT void 7504642e01fSmrgSetDeviceSpecificAccelerationProfile( 7514642e01fSmrg DeviceVelocityPtr s, 7524642e01fSmrg PointerAccelerationProfileFunc profile) 7534642e01fSmrg{ 7544642e01fSmrg if(s) 7554642e01fSmrg s->deviceSpecificProfile = profile; 7564642e01fSmrg} 7574642e01fSmrg 7584642e01fSmrg/** 7594642e01fSmrg * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if 7604642e01fSmrg * the predictable acceleration scheme is not in effect. 7614642e01fSmrg */ 7624642e01fSmrg_X_EXPORT DeviceVelocityPtr 7634642e01fSmrgGetDevicePredictableAccelData( 7644642e01fSmrg DeviceIntPtr pDev) 7654642e01fSmrg{ 7664642e01fSmrg /*sanity check*/ 7674642e01fSmrg if(!pDev){ 7684642e01fSmrg ErrorF("[dix] accel: DeviceIntPtr was NULL"); 7694642e01fSmrg return NULL; 7704642e01fSmrg } 7714642e01fSmrg if( pDev->valuator && 7724642e01fSmrg pDev->valuator->accelScheme.AccelSchemeProc == 7734642e01fSmrg acceleratePointerPredictable && 7744642e01fSmrg pDev->valuator->accelScheme.accelData != NULL){ 7754642e01fSmrg 7764642e01fSmrg return (DeviceVelocityPtr)pDev->valuator->accelScheme.accelData; 7774642e01fSmrg } 7784642e01fSmrg return NULL; 7794642e01fSmrg} 7804642e01fSmrg 7814642e01fSmrg/******************************** 7824642e01fSmrg * acceleration schemes 7834642e01fSmrg *******************************/ 7844642e01fSmrg 7854642e01fSmrg/** 7864642e01fSmrg * Modifies valuators in-place. 7874642e01fSmrg * This version employs a velocity approximation algorithm to 7884642e01fSmrg * enable fine-grained predictable acceleration profiles. 7894642e01fSmrg */ 7904642e01fSmrgvoid 7914642e01fSmrgacceleratePointerPredictable( 7924642e01fSmrg DeviceIntPtr pDev, 7934642e01fSmrg int first_valuator, 7944642e01fSmrg int num_valuators, 7954642e01fSmrg int *valuators, 7964642e01fSmrg int evtime) 7974642e01fSmrg{ 7984642e01fSmrg float mult = 0.0; 7994642e01fSmrg int dx = 0, dy = 0; 8004642e01fSmrg int *px = NULL, *py = NULL; 8014642e01fSmrg DeviceVelocityPtr velocitydata = 8024642e01fSmrg (DeviceVelocityPtr) pDev->valuator->accelScheme.accelData; 8034642e01fSmrg float fdx, fdy; /* no need to init */ 8044642e01fSmrg 8054642e01fSmrg if (!num_valuators || !valuators || !velocitydata) 8064642e01fSmrg return; 8074642e01fSmrg 8084642e01fSmrg if (first_valuator == 0) { 8094642e01fSmrg dx = valuators[0]; 8104642e01fSmrg px = &valuators[0]; 8114642e01fSmrg } 8124642e01fSmrg if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) { 8134642e01fSmrg dy = valuators[1 - first_valuator]; 8144642e01fSmrg py = &valuators[1 - first_valuator]; 8154642e01fSmrg } 8164642e01fSmrg 8174642e01fSmrg if (dx || dy){ 8184642e01fSmrg /* reset nonvisible state? */ 8194642e01fSmrg if (ProcessVelocityData(velocitydata, dx , dy, evtime)) { 8204642e01fSmrg /* set to center of pixel. makes sense as long as there are no 8214642e01fSmrg * means of passing on sub-pixel values. 8224642e01fSmrg */ 8234642e01fSmrg pDev->last.remainder[0] = pDev->last.remainder[1] = 0.5f; 8244642e01fSmrg /* prevent softening (somewhat quirky solution, 8254642e01fSmrg as it depends on the algorithm) */ 8264642e01fSmrg velocitydata->last_dx = dx; 8274642e01fSmrg velocitydata->last_dy = dy; 8284642e01fSmrg } 8294642e01fSmrg 8304642e01fSmrg if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) { 8314642e01fSmrg /* invoke acceleration profile to determine acceleration */ 8324642e01fSmrg mult = ComputeAcceleration (velocitydata, 8334642e01fSmrg pDev->ptrfeed->ctrl.threshold, 8344642e01fSmrg (float)pDev->ptrfeed->ctrl.num / 8354642e01fSmrg (float)pDev->ptrfeed->ctrl.den); 8364642e01fSmrg 8374642e01fSmrg if(mult != 1.0 || velocitydata->const_acceleration != 1.0) { 8384642e01fSmrg ApplySofteningAndConstantDeceleration( velocitydata, 8394642e01fSmrg dx, dy, 8404642e01fSmrg &fdx, &fdy, 8414642e01fSmrg mult > 1.0); 8424642e01fSmrg if (dx) { 8434642e01fSmrg pDev->last.remainder[0] = mult * fdx + pDev->last.remainder[0]; 8444642e01fSmrg *px = (int)pDev->last.remainder[0]; 8454642e01fSmrg pDev->last.remainder[0] = pDev->last.remainder[0] - (float)*px; 8464642e01fSmrg } 8474642e01fSmrg if (dy) { 8484642e01fSmrg pDev->last.remainder[1] = mult * fdy + pDev->last.remainder[1]; 8494642e01fSmrg *py = (int)pDev->last.remainder[1]; 8504642e01fSmrg pDev->last.remainder[1] = pDev->last.remainder[1] - (float)*py; 8514642e01fSmrg } 8524642e01fSmrg } 8534642e01fSmrg } 8544642e01fSmrg } 8554642e01fSmrg /* remember last motion delta (for softening/slow movement treatment) */ 8564642e01fSmrg velocitydata->last_dx = dx; 8574642e01fSmrg velocitydata->last_dy = dy; 8584642e01fSmrg} 8594642e01fSmrg 8604642e01fSmrg 8614642e01fSmrg 8624642e01fSmrg/** 8634642e01fSmrg * Originally a part of xf86PostMotionEvent; modifies valuators 8644642e01fSmrg * in-place. Retained mostly for embedded scenarios. 8654642e01fSmrg */ 8664642e01fSmrgvoid 8674642e01fSmrgacceleratePointerLightweight( 8684642e01fSmrg DeviceIntPtr pDev, 8694642e01fSmrg int first_valuator, 8704642e01fSmrg int num_valuators, 8714642e01fSmrg int *valuators, 8724642e01fSmrg int ignored) 8734642e01fSmrg{ 8744642e01fSmrg float mult = 0.0; 8754642e01fSmrg int dx = 0, dy = 0; 8764642e01fSmrg int *px = NULL, *py = NULL; 8774642e01fSmrg 8784642e01fSmrg if (!num_valuators || !valuators) 8794642e01fSmrg return; 8804642e01fSmrg 8814642e01fSmrg if (first_valuator == 0) { 8824642e01fSmrg dx = valuators[0]; 8834642e01fSmrg px = &valuators[0]; 8844642e01fSmrg } 8854642e01fSmrg if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) { 8864642e01fSmrg dy = valuators[1 - first_valuator]; 8874642e01fSmrg py = &valuators[1 - first_valuator]; 8884642e01fSmrg } 8894642e01fSmrg 8904642e01fSmrg if (!dx && !dy) 8914642e01fSmrg return; 8924642e01fSmrg 8934642e01fSmrg if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) { 8944642e01fSmrg /* modeled from xf86Events.c */ 8954642e01fSmrg if (pDev->ptrfeed->ctrl.threshold) { 8964642e01fSmrg if ((abs(dx) + abs(dy)) >= pDev->ptrfeed->ctrl.threshold) { 8974642e01fSmrg pDev->last.remainder[0] = ((float)dx * 8984642e01fSmrg (float)(pDev->ptrfeed->ctrl.num)) / 8994642e01fSmrg (float)(pDev->ptrfeed->ctrl.den) + 9004642e01fSmrg pDev->last.remainder[0]; 9014642e01fSmrg if (px) { 9024642e01fSmrg *px = (int)pDev->last.remainder[0]; 9034642e01fSmrg pDev->last.remainder[0] = pDev->last.remainder[0] - 9044642e01fSmrg (float)(*px); 9054642e01fSmrg } 9064642e01fSmrg 9074642e01fSmrg pDev->last.remainder[1] = ((float)dy * 9084642e01fSmrg (float)(pDev->ptrfeed->ctrl.num)) / 9094642e01fSmrg (float)(pDev->ptrfeed->ctrl.den) + 9104642e01fSmrg pDev->last.remainder[1]; 9114642e01fSmrg if (py) { 9124642e01fSmrg *py = (int)pDev->last.remainder[1]; 9134642e01fSmrg pDev->last.remainder[1] = pDev->last.remainder[1] - 9144642e01fSmrg (float)(*py); 9154642e01fSmrg } 9164642e01fSmrg } 9174642e01fSmrg } 9184642e01fSmrg else { 9194642e01fSmrg mult = pow((float)dx * (float)dx + (float)dy * (float)dy, 9204642e01fSmrg ((float)(pDev->ptrfeed->ctrl.num) / 9214642e01fSmrg (float)(pDev->ptrfeed->ctrl.den) - 1.0) / 9224642e01fSmrg 2.0) / 2.0; 9234642e01fSmrg if (dx) { 9244642e01fSmrg pDev->last.remainder[0] = mult * (float)dx + 9254642e01fSmrg pDev->last.remainder[0]; 9264642e01fSmrg *px = (int)pDev->last.remainder[0]; 9274642e01fSmrg pDev->last.remainder[0] = pDev->last.remainder[0] - 9284642e01fSmrg (float)(*px); 9294642e01fSmrg } 9304642e01fSmrg if (dy) { 9314642e01fSmrg pDev->last.remainder[1] = mult * (float)dy + 9324642e01fSmrg pDev->last.remainder[1]; 9334642e01fSmrg *py = (int)pDev->last.remainder[1]; 9344642e01fSmrg pDev->last.remainder[1] = pDev->last.remainder[1] - 9354642e01fSmrg (float)(*py); 9364642e01fSmrg } 9374642e01fSmrg } 9384642e01fSmrg } 9394642e01fSmrg} 940