ptrveloc.c revision 35c4bbdf
14642e01fSmrg/* 24642e01fSmrg * 36747b715Smrg * Copyright © 2006-2009 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> 316747b715Smrg#include <exevents.h> 326747b715Smrg#include <X11/Xatom.h> 3335c4bbdfSmrg#include <os.h> 346747b715Smrg 356747b715Smrg#include <xserver-properties.h> 364642e01fSmrg 374642e01fSmrg/***************************************************************************** 384642e01fSmrg * Predictable pointer acceleration 394642e01fSmrg * 406747b715Smrg * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de) 414642e01fSmrg * 424642e01fSmrg * Serves 3 complementary functions: 434642e01fSmrg * 1) provide a sophisticated ballistic velocity estimate to improve 444642e01fSmrg * the relation between velocity (of the device) and acceleration 454642e01fSmrg * 2) make arbitrary acceleration profiles possible 464642e01fSmrg * 3) decelerate by two means (constant and adaptive) if enabled 474642e01fSmrg * 484642e01fSmrg * Important concepts are the 494642e01fSmrg * 504642e01fSmrg * - Scheme 514642e01fSmrg * which selects the basic algorithm 524642e01fSmrg * (see devices.c/InitPointerAccelerationScheme) 534642e01fSmrg * - Profile 544642e01fSmrg * which returns an acceleration 554642e01fSmrg * for a given velocity 564642e01fSmrg * 576747b715Smrg * The profile can be selected by the user at runtime. 586747b715Smrg * The classic profile is intended to cleanly perform old-style 594642e01fSmrg * function selection (threshold =/!= 0) 604642e01fSmrg * 614642e01fSmrg ****************************************************************************/ 624642e01fSmrg 634642e01fSmrg/* fwds */ 6435c4bbdfSmrgstatic double 6535c4bbdfSmrgSimpleSmoothProfile(DeviceIntPtr dev, DeviceVelocityPtr vel, double velocity, 6635c4bbdfSmrg double threshold, double acc); 676747b715Smrgstatic PointerAccelerationProfileFunc 686747b715SmrgGetAccelerationProfile(DeviceVelocityPtr vel, int profile_num); 6935c4bbdfSmrgstatic BOOL 7035c4bbdfSmrgInitializePredictableAccelerationProperties(DeviceIntPtr, 7135c4bbdfSmrg DeviceVelocityPtr, 7235c4bbdfSmrg PredictableAccelSchemePtr); 7335c4bbdfSmrgstatic BOOL 7435c4bbdfSmrgDeletePredictableAccelerationProperties(DeviceIntPtr, 7535c4bbdfSmrg PredictableAccelSchemePtr); 764642e01fSmrg 774642e01fSmrg/*#define PTRACCEL_DEBUGGING*/ 784642e01fSmrg 794642e01fSmrg#ifdef PTRACCEL_DEBUGGING 8035c4bbdfSmrg#define DebugAccelF(...) ErrorFSigSafe("dix/ptraccel: " __VA_ARGS__) 814642e01fSmrg#else 8235c4bbdfSmrg#define DebugAccelF(...) /* */ 834642e01fSmrg#endif 844642e01fSmrg 854642e01fSmrg/******************************** 866747b715Smrg * Init/Uninit 874642e01fSmrg *******************************/ 884642e01fSmrg 896747b715Smrg/* some int which is not a profile number */ 906747b715Smrg#define PROFILE_UNINITIALIZE (-100) 916747b715Smrg 924642e01fSmrg/** 9335c4bbdfSmrg * Init DeviceVelocity struct so it should match the average case 944642e01fSmrg */ 954642e01fSmrgvoid 966747b715SmrgInitVelocityData(DeviceVelocityPtr vel) 974642e01fSmrg{ 986747b715Smrg memset(vel, 0, sizeof(DeviceVelocityRec)); 996747b715Smrg 10035c4bbdfSmrg vel->corr_mul = 10.0; /* dots per 10 milisecond should be usable */ 10135c4bbdfSmrg vel->const_acceleration = 1.0; /* no acceleration/deceleration */ 1026747b715Smrg vel->reset_time = 300; 1036747b715Smrg vel->use_softening = 1; 10435c4bbdfSmrg vel->min_acceleration = 1.0; /* don't decelerate */ 1056747b715Smrg vel->max_rel_diff = 0.2; 1066747b715Smrg vel->max_diff = 1.0; 1076747b715Smrg vel->initial_range = 2; 1086747b715Smrg vel->average_accel = TRUE; 1096747b715Smrg SetAccelerationProfile(vel, AccelProfileClassic); 1106747b715Smrg InitTrackers(vel, 16); 1114642e01fSmrg} 1124642e01fSmrg 1134642e01fSmrg/** 11435c4bbdfSmrg * Clean up DeviceVelocityRec 1154642e01fSmrg */ 1166747b715Smrgvoid 11735c4bbdfSmrgFreeVelocityData(DeviceVelocityPtr vel) 11835c4bbdfSmrg{ 1196747b715Smrg free(vel->tracker); 1206747b715Smrg SetAccelerationProfile(vel, PROFILE_UNINITIALIZE); 1214642e01fSmrg} 1224642e01fSmrg 12335c4bbdfSmrg/** 12435c4bbdfSmrg * Init predictable scheme 12535c4bbdfSmrg */ 12635c4bbdfSmrgBool 12735c4bbdfSmrgInitPredictableAccelerationScheme(DeviceIntPtr dev, 12835c4bbdfSmrg ValuatorAccelerationPtr protoScheme) 12935c4bbdfSmrg{ 13035c4bbdfSmrg DeviceVelocityPtr vel; 13135c4bbdfSmrg ValuatorAccelerationRec scheme; 13235c4bbdfSmrg PredictableAccelSchemePtr schemeData; 13335c4bbdfSmrg 13435c4bbdfSmrg scheme = *protoScheme; 13535c4bbdfSmrg vel = calloc(1, sizeof(DeviceVelocityRec)); 13635c4bbdfSmrg schemeData = calloc(1, sizeof(PredictableAccelSchemeRec)); 13735c4bbdfSmrg if (!vel || !schemeData) { 13835c4bbdfSmrg free(vel); 13935c4bbdfSmrg free(schemeData); 14035c4bbdfSmrg return FALSE; 14135c4bbdfSmrg } 14235c4bbdfSmrg InitVelocityData(vel); 14335c4bbdfSmrg schemeData->vel = vel; 14435c4bbdfSmrg scheme.accelData = schemeData; 14535c4bbdfSmrg if (!InitializePredictableAccelerationProperties(dev, vel, schemeData)) { 14635c4bbdfSmrg free(vel); 14735c4bbdfSmrg free(schemeData); 14835c4bbdfSmrg return FALSE; 14935c4bbdfSmrg } 15035c4bbdfSmrg /* all fine, assign scheme to device */ 15135c4bbdfSmrg dev->valuator->accelScheme = scheme; 15235c4bbdfSmrg return TRUE; 15335c4bbdfSmrg} 1544642e01fSmrg 15535c4bbdfSmrg/** 15635c4bbdfSmrg * Uninit scheme 1574642e01fSmrg */ 1584642e01fSmrgvoid 1596747b715SmrgAccelerationDefaultCleanup(DeviceIntPtr dev) 1604642e01fSmrg{ 16135c4bbdfSmrg DeviceVelocityPtr vel = GetDevicePredictableAccelData(dev); 16235c4bbdfSmrg 16335c4bbdfSmrg if (vel) { 16435c4bbdfSmrg /* the proper guarantee would be that we're not inside of 16535c4bbdfSmrg * AccelSchemeProc(), but that seems impossible. Schemes don't get 16635c4bbdfSmrg * switched often anyway. 16735c4bbdfSmrg */ 16835c4bbdfSmrg OsBlockSignals(); 1696747b715Smrg dev->valuator->accelScheme.AccelSchemeProc = NULL; 17035c4bbdfSmrg FreeVelocityData(vel); 17135c4bbdfSmrg free(vel); 17235c4bbdfSmrg DeletePredictableAccelerationProperties(dev, 17335c4bbdfSmrg (PredictableAccelSchemePtr) 17435c4bbdfSmrg dev->valuator->accelScheme. 17535c4bbdfSmrg accelData); 1766747b715Smrg free(dev->valuator->accelScheme.accelData); 1776747b715Smrg dev->valuator->accelScheme.accelData = NULL; 17835c4bbdfSmrg OsReleaseSignals(); 1794642e01fSmrg } 1804642e01fSmrg} 1814642e01fSmrg 1826747b715Smrg/************************* 1836747b715Smrg * Input property support 1846747b715Smrg ************************/ 1854642e01fSmrg 1864642e01fSmrg/** 1876747b715Smrg * choose profile 1886747b715Smrg */ 1896747b715Smrgstatic int 1906747b715SmrgAccelSetProfileProperty(DeviceIntPtr dev, Atom atom, 1916747b715Smrg XIPropertyValuePtr val, BOOL checkOnly) 1924642e01fSmrg{ 1936747b715Smrg DeviceVelocityPtr vel; 1946747b715Smrg int profile, *ptr = &profile; 1956747b715Smrg int rc; 1966747b715Smrg int nelem = 1; 1976747b715Smrg 1986747b715Smrg if (atom != XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER)) 1996747b715Smrg return Success; 2006747b715Smrg 2016747b715Smrg vel = GetDevicePredictableAccelData(dev); 2026747b715Smrg if (!vel) 2036747b715Smrg return BadValue; 2046747b715Smrg rc = XIPropToInt(val, &nelem, &ptr); 2056747b715Smrg 20635c4bbdfSmrg if (checkOnly) { 2076747b715Smrg if (rc) 2086747b715Smrg return rc; 2096747b715Smrg 2106747b715Smrg if (GetAccelerationProfile(vel, profile) == NULL) 2116747b715Smrg return BadValue; 21235c4bbdfSmrg } 21335c4bbdfSmrg else 21435c4bbdfSmrg SetAccelerationProfile(vel, profile); 2156747b715Smrg 2166747b715Smrg return Success; 2174642e01fSmrg} 2184642e01fSmrg 2196747b715Smrgstatic long 2206747b715SmrgAccelInitProfileProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 2214642e01fSmrg{ 2226747b715Smrg int profile = vel->statistics.profile_number; 2236747b715Smrg Atom prop_profile_number = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); 2244642e01fSmrg 2256747b715Smrg XIChangeDeviceProperty(dev, prop_profile_number, XA_INTEGER, 32, 2266747b715Smrg PropModeReplace, 1, &profile, FALSE); 2276747b715Smrg XISetDevicePropertyDeletable(dev, prop_profile_number, FALSE); 2286747b715Smrg return XIRegisterPropertyHandler(dev, AccelSetProfileProperty, NULL, NULL); 2294642e01fSmrg} 2304642e01fSmrg 2316747b715Smrg/** 2326747b715Smrg * constant deceleration 2336747b715Smrg */ 2346747b715Smrgstatic int 2356747b715SmrgAccelSetDecelProperty(DeviceIntPtr dev, Atom atom, 2366747b715Smrg XIPropertyValuePtr val, BOOL checkOnly) 2374642e01fSmrg{ 2386747b715Smrg DeviceVelocityPtr vel; 2396747b715Smrg float v, *ptr = &v; 2406747b715Smrg int rc; 2416747b715Smrg int nelem = 1; 2426747b715Smrg 2436747b715Smrg if (atom != XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION)) 2446747b715Smrg return Success; 2456747b715Smrg 2466747b715Smrg vel = GetDevicePredictableAccelData(dev); 2476747b715Smrg if (!vel) 2486747b715Smrg return BadValue; 2496747b715Smrg rc = XIPropToFloat(val, &nelem, &ptr); 2506747b715Smrg 25135c4bbdfSmrg if (checkOnly) { 2526747b715Smrg if (rc) 2536747b715Smrg return rc; 25435c4bbdfSmrg return (v > 0) ? Success : BadValue; 2554642e01fSmrg } 2566747b715Smrg 25735c4bbdfSmrg vel->const_acceleration = 1 / v; 2586747b715Smrg 2596747b715Smrg return Success; 2606747b715Smrg} 2616747b715Smrg 2626747b715Smrgstatic long 2636747b715SmrgAccelInitDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 2646747b715Smrg{ 26535c4bbdfSmrg float fval = 1.0 / vel->const_acceleration; 26635c4bbdfSmrg Atom prop_const_decel = 26735c4bbdfSmrg XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); 2686747b715Smrg XIChangeDeviceProperty(dev, prop_const_decel, 26935c4bbdfSmrg XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 27035c4bbdfSmrg 1, &fval, FALSE); 2716747b715Smrg XISetDevicePropertyDeletable(dev, prop_const_decel, FALSE); 2726747b715Smrg return XIRegisterPropertyHandler(dev, AccelSetDecelProperty, NULL, NULL); 2734642e01fSmrg} 2744642e01fSmrg 2754642e01fSmrg/** 2766747b715Smrg * adaptive deceleration 2774642e01fSmrg */ 2786747b715Smrgstatic int 2796747b715SmrgAccelSetAdaptDecelProperty(DeviceIntPtr dev, Atom atom, 2806747b715Smrg XIPropertyValuePtr val, BOOL checkOnly) 2814642e01fSmrg{ 2826747b715Smrg DeviceVelocityPtr veloc; 2836747b715Smrg float v, *ptr = &v; 2846747b715Smrg int rc; 2856747b715Smrg int nelem = 1; 2866747b715Smrg 2876747b715Smrg if (atom != XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION)) 2886747b715Smrg return Success; 2896747b715Smrg 2906747b715Smrg veloc = GetDevicePredictableAccelData(dev); 2916747b715Smrg if (!veloc) 2926747b715Smrg return BadValue; 2936747b715Smrg rc = XIPropToFloat(val, &nelem, &ptr); 2946747b715Smrg 29535c4bbdfSmrg if (checkOnly) { 2966747b715Smrg if (rc) 2976747b715Smrg return rc; 29835c4bbdfSmrg return (v >= 1.0f) ? Success : BadValue; 2994642e01fSmrg } 3006747b715Smrg 30135c4bbdfSmrg if (v >= 1.0f) 30235c4bbdfSmrg veloc->min_acceleration = 1 / v; 3036747b715Smrg 3046747b715Smrg return Success; 3054642e01fSmrg} 3064642e01fSmrg 3076747b715Smrgstatic long 3086747b715SmrgAccelInitAdaptDecelProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 3096747b715Smrg{ 31035c4bbdfSmrg float fval = 1.0 / vel->min_acceleration; 31135c4bbdfSmrg Atom prop_adapt_decel = 31235c4bbdfSmrg XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); 3134642e01fSmrg 31435c4bbdfSmrg XIChangeDeviceProperty(dev, prop_adapt_decel, 31535c4bbdfSmrg XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 31635c4bbdfSmrg 1, &fval, FALSE); 3176747b715Smrg XISetDevicePropertyDeletable(dev, prop_adapt_decel, FALSE); 31835c4bbdfSmrg return XIRegisterPropertyHandler(dev, AccelSetAdaptDecelProperty, NULL, 31935c4bbdfSmrg NULL); 3206747b715Smrg} 3216747b715Smrg 3226747b715Smrg/** 3236747b715Smrg * velocity scaling 3246747b715Smrg */ 3256747b715Smrgstatic int 3266747b715SmrgAccelSetScaleProperty(DeviceIntPtr dev, Atom atom, 3276747b715Smrg XIPropertyValuePtr val, BOOL checkOnly) 3284642e01fSmrg{ 3296747b715Smrg DeviceVelocityPtr vel; 3306747b715Smrg float v, *ptr = &v; 3316747b715Smrg int rc; 3326747b715Smrg int nelem = 1; 3336747b715Smrg 3346747b715Smrg if (atom != XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING)) 3356747b715Smrg return Success; 3364642e01fSmrg 3376747b715Smrg vel = GetDevicePredictableAccelData(dev); 3386747b715Smrg if (!vel) 3396747b715Smrg return BadValue; 3406747b715Smrg rc = XIPropToFloat(val, &nelem, &ptr); 3416747b715Smrg 34235c4bbdfSmrg if (checkOnly) { 3436747b715Smrg if (rc) 3446747b715Smrg return rc; 3456747b715Smrg 3466747b715Smrg return (v > 0) ? Success : BadValue; 3474642e01fSmrg } 3486747b715Smrg 34935c4bbdfSmrg if (v > 0) 35035c4bbdfSmrg vel->corr_mul = v; 3516747b715Smrg 3526747b715Smrg return Success; 3534642e01fSmrg} 3544642e01fSmrg 3556747b715Smrgstatic long 3566747b715SmrgAccelInitScaleProperty(DeviceIntPtr dev, DeviceVelocityPtr vel) 3576747b715Smrg{ 3586747b715Smrg float fval = vel->corr_mul; 3596747b715Smrg Atom prop_velo_scale = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); 3604642e01fSmrg 36135c4bbdfSmrg XIChangeDeviceProperty(dev, prop_velo_scale, 36235c4bbdfSmrg XIGetKnownProperty(XATOM_FLOAT), 32, PropModeReplace, 36335c4bbdfSmrg 1, &fval, FALSE); 3646747b715Smrg XISetDevicePropertyDeletable(dev, prop_velo_scale, FALSE); 3656747b715Smrg return XIRegisterPropertyHandler(dev, AccelSetScaleProperty, NULL, NULL); 3664642e01fSmrg} 3674642e01fSmrg 36835c4bbdfSmrgstatic BOOL 36935c4bbdfSmrgInitializePredictableAccelerationProperties(DeviceIntPtr dev, 37035c4bbdfSmrg DeviceVelocityPtr vel, 37135c4bbdfSmrg PredictableAccelSchemePtr 37235c4bbdfSmrg schemeData) 3734642e01fSmrg{ 37435c4bbdfSmrg int num_handlers = 4; 3754642e01fSmrg 37635c4bbdfSmrg if (!vel) 37735c4bbdfSmrg return FALSE; 3784642e01fSmrg 37935c4bbdfSmrg schemeData->prop_handlers = calloc(num_handlers, sizeof(long)); 38035c4bbdfSmrg if (!schemeData->prop_handlers) 38135c4bbdfSmrg return FALSE; 38235c4bbdfSmrg schemeData->num_prop_handlers = num_handlers; 38335c4bbdfSmrg schemeData->prop_handlers[0] = AccelInitProfileProperty(dev, vel); 38435c4bbdfSmrg schemeData->prop_handlers[1] = AccelInitDecelProperty(dev, vel); 38535c4bbdfSmrg schemeData->prop_handlers[2] = AccelInitAdaptDecelProperty(dev, vel); 38635c4bbdfSmrg schemeData->prop_handlers[3] = AccelInitScaleProperty(dev, vel); 3874642e01fSmrg 3886747b715Smrg return TRUE; 3896747b715Smrg} 3904642e01fSmrg 3916747b715SmrgBOOL 39235c4bbdfSmrgDeletePredictableAccelerationProperties(DeviceIntPtr dev, 39335c4bbdfSmrg PredictableAccelSchemePtr scheme) 3946747b715Smrg{ 39535c4bbdfSmrg DeviceVelocityPtr vel; 3966747b715Smrg Atom prop; 3976747b715Smrg int i; 3986747b715Smrg 3996747b715Smrg prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); 4006747b715Smrg XIDeleteDeviceProperty(dev, prop, FALSE); 4016747b715Smrg prop = XIGetKnownProperty(ACCEL_PROP_ADAPTIVE_DECELERATION); 4026747b715Smrg XIDeleteDeviceProperty(dev, prop, FALSE); 4036747b715Smrg prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION); 4046747b715Smrg XIDeleteDeviceProperty(dev, prop, FALSE); 4056747b715Smrg prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER); 4066747b715Smrg XIDeleteDeviceProperty(dev, prop, FALSE); 4076747b715Smrg 4086747b715Smrg vel = GetDevicePredictableAccelData(dev); 40935c4bbdfSmrg if (vel) { 41035c4bbdfSmrg for (i = 0; i < scheme->num_prop_handlers; i++) 41135c4bbdfSmrg if (scheme->prop_handlers[i]) 41235c4bbdfSmrg XIUnregisterPropertyHandler(dev, scheme->prop_handlers[i]); 41335c4bbdfSmrg } 4144642e01fSmrg 41535c4bbdfSmrg free(scheme->prop_handlers); 41635c4bbdfSmrg scheme->prop_handlers = NULL; 41735c4bbdfSmrg scheme->num_prop_handlers = 0; 4186747b715Smrg return TRUE; 4194642e01fSmrg} 4204642e01fSmrg 4216747b715Smrg/********************* 4226747b715Smrg * Tracking logic 4236747b715Smrg ********************/ 4244642e01fSmrg 4256747b715Smrgvoid 4266747b715SmrgInitTrackers(DeviceVelocityPtr vel, int ntracker) 4276747b715Smrg{ 42835c4bbdfSmrg if (ntracker < 1) { 42935c4bbdfSmrg ErrorF("invalid number of trackers\n"); 43035c4bbdfSmrg return; 4314642e01fSmrg } 4326747b715Smrg free(vel->tracker); 43335c4bbdfSmrg vel->tracker = (MotionTrackerPtr) calloc(ntracker, sizeof(MotionTracker)); 4346747b715Smrg vel->num_tracker = ntracker; 4354642e01fSmrg} 4364642e01fSmrg 43735c4bbdfSmrgenum directions { 43835c4bbdfSmrg N = (1 << 0), 43935c4bbdfSmrg NE = (1 << 1), 44035c4bbdfSmrg E = (1 << 2), 44135c4bbdfSmrg SE = (1 << 3), 44235c4bbdfSmrg S = (1 << 4), 44335c4bbdfSmrg SW = (1 << 5), 44435c4bbdfSmrg W = (1 << 6), 44535c4bbdfSmrg NW = (1 << 7), 44635c4bbdfSmrg UNDEFINED = 0xFF 44735c4bbdfSmrg}; 44835c4bbdfSmrg 4494642e01fSmrg/** 4506747b715Smrg * return a bit field of possible directions. 4516747b715Smrg * There's no reason against widening to more precise directions (<45 degrees), 4526747b715Smrg * should it not perform well. All this is needed for is sort out non-linear 4536747b715Smrg * motion, so precision isn't paramount. However, one should not flag direction 4546747b715Smrg * too narrow, since it would then cut the linear segment to zero size way too 4556747b715Smrg * often. 45635c4bbdfSmrg * 45735c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for 45835c4bbdfSmrg * this movement. 4594642e01fSmrg */ 4606747b715Smrgstatic int 46135c4bbdfSmrgDoGetDirection(int dx, int dy) 46235c4bbdfSmrg{ 46335c4bbdfSmrg int dir = 0; 46435c4bbdfSmrg 4656747b715Smrg /* on insignificant mickeys, flag 135 degrees */ 46635c4bbdfSmrg if (abs(dx) < 2 && abs(dy) < 2) { 46735c4bbdfSmrg /* first check diagonal cases */ 46835c4bbdfSmrg if (dx > 0 && dy > 0) 46935c4bbdfSmrg dir = E | SE | S; 47035c4bbdfSmrg else if (dx > 0 && dy < 0) 47135c4bbdfSmrg dir = N | NE | E; 47235c4bbdfSmrg else if (dx < 0 && dy < 0) 47335c4bbdfSmrg dir = W | NW | N; 47435c4bbdfSmrg else if (dx < 0 && dy > 0) 47535c4bbdfSmrg dir = W | SW | S; 4766747b715Smrg /* check axis-aligned directions */ 47735c4bbdfSmrg else if (dx > 0) 47835c4bbdfSmrg dir = NE | E | SE; 47935c4bbdfSmrg else if (dx < 0) 48035c4bbdfSmrg dir = NW | W | SW; 48135c4bbdfSmrg else if (dy > 0) 48235c4bbdfSmrg dir = SE | S | SW; 48335c4bbdfSmrg else if (dy < 0) 48435c4bbdfSmrg dir = NE | N | NW; 48535c4bbdfSmrg else 48635c4bbdfSmrg dir = UNDEFINED; /* shouldn't happen */ 4874642e01fSmrg } 48835c4bbdfSmrg else { /* compute angle and set appropriate flags */ 48935c4bbdfSmrg double r; 49035c4bbdfSmrg int i1, i2; 49135c4bbdfSmrg 49235c4bbdfSmrg r = atan2(dy, dx); 49335c4bbdfSmrg /* find direction. 49435c4bbdfSmrg * 49535c4bbdfSmrg * Add 360° to avoid r become negative since C has no well-defined 49635c4bbdfSmrg * modulo for such cases. Then divide by 45° to get the octant 49735c4bbdfSmrg * number, e.g. 49835c4bbdfSmrg * 0 <= r <= 1 is [0-45]° 49935c4bbdfSmrg * 1 <= r <= 2 is [45-90]° 50035c4bbdfSmrg * etc. 50135c4bbdfSmrg * But we add extra 90° to match up with our N, S, etc. defines up 50235c4bbdfSmrg * there, rest stays the same. 50335c4bbdfSmrg */ 50435c4bbdfSmrg r = (r + (M_PI * 2.5)) / (M_PI / 4); 50535c4bbdfSmrg /* this intends to flag 2 directions (45 degrees), 50635c4bbdfSmrg * except on very well-aligned mickeys. */ 50735c4bbdfSmrg i1 = (int) (r + 0.1) % 8; 50835c4bbdfSmrg i2 = (int) (r + 0.9) % 8; 50935c4bbdfSmrg if (i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7) 51035c4bbdfSmrg dir = UNDEFINED; /* shouldn't happen */ 51135c4bbdfSmrg else 51235c4bbdfSmrg dir = (1 << i1 | 1 << i2); 51335c4bbdfSmrg } 51435c4bbdfSmrg return dir; 5156747b715Smrg} 5164642e01fSmrg 5176747b715Smrg#define DIRECTION_CACHE_RANGE 5 5186747b715Smrg#define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1) 5196747b715Smrg 52035c4bbdfSmrg/* cache DoGetDirection(). 52135c4bbdfSmrg * To avoid excessive use of direction calculation, cache the values for 52235c4bbdfSmrg * [-5..5] for both x/y. Anything outside of that is calcualted on the fly. 52335c4bbdfSmrg * 52435c4bbdfSmrg * @return A bitmask for N, NE, S, SE, etc. indicating the directions for 52535c4bbdfSmrg * this movement. 52635c4bbdfSmrg */ 5276747b715Smrgstatic int 52835c4bbdfSmrgGetDirection(int dx, int dy) 52935c4bbdfSmrg{ 5306747b715Smrg static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE]; 53135c4bbdfSmrg int dir; 53235c4bbdfSmrg 53335c4bbdfSmrg if (abs(dx) <= DIRECTION_CACHE_RANGE && abs(dy) <= DIRECTION_CACHE_RANGE) { 53435c4bbdfSmrg /* cacheable */ 53535c4bbdfSmrg dir = cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy]; 53635c4bbdfSmrg if (dir == 0) { 53735c4bbdfSmrg dir = DoGetDirection(dx, dy); 53835c4bbdfSmrg cache[DIRECTION_CACHE_RANGE + dx][DIRECTION_CACHE_RANGE + dy] = dir; 53935c4bbdfSmrg } 5404642e01fSmrg } 54135c4bbdfSmrg else { 54235c4bbdfSmrg /* non-cacheable */ 54335c4bbdfSmrg dir = DoGetDirection(dx, dy); 54435c4bbdfSmrg } 54535c4bbdfSmrg 54635c4bbdfSmrg return dir; 5476747b715Smrg} 5484642e01fSmrg 5496747b715Smrg#undef DIRECTION_CACHE_RANGE 5506747b715Smrg#undef DIRECTION_CACHE_SIZE 5514642e01fSmrg 5526747b715Smrg/* convert offset (age) to array index */ 5536747b715Smrg#define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker) 55435c4bbdfSmrg#define TRACKER(s, d) &(s)->tracker[TRACKER_INDEX(s,d)] 5556747b715Smrg 55635c4bbdfSmrg/** 55735c4bbdfSmrg * Add the delta motion to each tracker, then reset the latest tracker to 55835c4bbdfSmrg * 0/0 and set it as the current one. 55935c4bbdfSmrg */ 5606747b715Smrgstatic inline void 56135c4bbdfSmrgFeedTrackers(DeviceVelocityPtr vel, double dx, double dy, int cur_t) 5626747b715Smrg{ 5636747b715Smrg int n; 56435c4bbdfSmrg 56535c4bbdfSmrg for (n = 0; n < vel->num_tracker; n++) { 56635c4bbdfSmrg vel->tracker[n].dx += dx; 56735c4bbdfSmrg vel->tracker[n].dy += dy; 5684642e01fSmrg } 5696747b715Smrg n = (vel->cur_tracker + 1) % vel->num_tracker; 57035c4bbdfSmrg vel->tracker[n].dx = 0.0; 57135c4bbdfSmrg vel->tracker[n].dy = 0.0; 5726747b715Smrg vel->tracker[n].time = cur_t; 5736747b715Smrg vel->tracker[n].dir = GetDirection(dx, dy); 57435c4bbdfSmrg DebugAccelF("motion [dx: %f dy: %f dir:%d diff: %d]\n", 5756747b715Smrg dx, dy, vel->tracker[n].dir, 5766747b715Smrg cur_t - vel->tracker[vel->cur_tracker].time); 5776747b715Smrg vel->cur_tracker = n; 5786747b715Smrg} 5796747b715Smrg 5806747b715Smrg/** 5816747b715Smrg * calc velocity for given tracker, with 5826747b715Smrg * velocity scaling. 5836747b715Smrg * This assumes linear motion. 5846747b715Smrg */ 58535c4bbdfSmrgstatic double 58635c4bbdfSmrgCalcTracker(const MotionTracker * tracker, int cur_t) 58735c4bbdfSmrg{ 58835c4bbdfSmrg double dist = sqrt(tracker->dx * tracker->dx + tracker->dy * tracker->dy); 58935c4bbdfSmrg int dtime = cur_t - tracker->time; 59035c4bbdfSmrg 59135c4bbdfSmrg if (dtime > 0) 59235c4bbdfSmrg return dist / dtime; 5936747b715Smrg else 59435c4bbdfSmrg return 0; /* synonymous for NaN, since we're not C99 */ 5956747b715Smrg} 5966747b715Smrg 5976747b715Smrg/* find the most plausible velocity. That is, the most distant 59835c4bbdfSmrg * (in time) tracker which isn't too old, the movement vector was 59935c4bbdfSmrg * in the same octant, and where the velocity is within an 60035c4bbdfSmrg * acceptable range to the inital velocity. 6016747b715Smrg * 60235c4bbdfSmrg * @return The tracker's velocity or 0 if the above conditions are unmet 6036747b715Smrg */ 60435c4bbdfSmrgstatic double 60535c4bbdfSmrgQueryTrackers(DeviceVelocityPtr vel, int cur_t) 60635c4bbdfSmrg{ 60735c4bbdfSmrg int offset, dir = UNDEFINED, used_offset = -1, age_ms; 60835c4bbdfSmrg 6096747b715Smrg /* initial velocity: a low-offset, valid velocity */ 61035c4bbdfSmrg double initial_velocity = 0, result = 0, velocity_diff; 61135c4bbdfSmrg double velocity_factor = vel->corr_mul * vel->const_acceleration; /* premultiply */ 61235c4bbdfSmrg 6136747b715Smrg /* loop from current to older data */ 61435c4bbdfSmrg for (offset = 1; offset < vel->num_tracker; offset++) { 61535c4bbdfSmrg MotionTracker *tracker = TRACKER(vel, offset); 61635c4bbdfSmrg double tracker_velocity; 61735c4bbdfSmrg 61835c4bbdfSmrg age_ms = cur_t - tracker->time; 61935c4bbdfSmrg 62035c4bbdfSmrg /* bail out if data is too old and protect from overrun */ 62135c4bbdfSmrg if (age_ms >= vel->reset_time || age_ms < 0) { 62235c4bbdfSmrg DebugAccelF("query: tracker too old (reset after %d, age is %d)\n", 62335c4bbdfSmrg vel->reset_time, age_ms); 62435c4bbdfSmrg break; 62535c4bbdfSmrg } 62635c4bbdfSmrg 62735c4bbdfSmrg /* 62835c4bbdfSmrg * this heuristic avoids using the linear-motion velocity formula 62935c4bbdfSmrg * in CalcTracker() on motion that isn't exactly linear. So to get 63035c4bbdfSmrg * even more precision we could subdivide as a final step, so possible 63135c4bbdfSmrg * non-linearities are accounted for. 63235c4bbdfSmrg */ 63335c4bbdfSmrg dir &= tracker->dir; 63435c4bbdfSmrg if (dir == 0) { /* we've changed octant of movement (e.g. NE → NW) */ 63535c4bbdfSmrg DebugAccelF("query: no longer linear\n"); 63635c4bbdfSmrg /* instead of breaking it we might also inspect the partition after, 63735c4bbdfSmrg * but actual improvement with this is probably rare. */ 63835c4bbdfSmrg break; 63935c4bbdfSmrg } 64035c4bbdfSmrg 64135c4bbdfSmrg tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor; 64235c4bbdfSmrg 64335c4bbdfSmrg if ((initial_velocity == 0 || offset <= vel->initial_range) && 64435c4bbdfSmrg tracker_velocity != 0) { 64535c4bbdfSmrg /* set initial velocity and result */ 64635c4bbdfSmrg result = initial_velocity = tracker_velocity; 64735c4bbdfSmrg used_offset = offset; 64835c4bbdfSmrg } 64935c4bbdfSmrg else if (initial_velocity != 0 && tracker_velocity != 0) { 65035c4bbdfSmrg velocity_diff = fabs(initial_velocity - tracker_velocity); 65135c4bbdfSmrg 65235c4bbdfSmrg if (velocity_diff > vel->max_diff && 65335c4bbdfSmrg velocity_diff / (initial_velocity + tracker_velocity) >= 65435c4bbdfSmrg vel->max_rel_diff) { 65535c4bbdfSmrg /* we're not in range, quit - it won't get better. */ 65635c4bbdfSmrg DebugAccelF("query: tracker too different:" 65735c4bbdfSmrg " old %2.2f initial %2.2f diff: %2.2f\n", 65835c4bbdfSmrg tracker_velocity, initial_velocity, velocity_diff); 65935c4bbdfSmrg break; 66035c4bbdfSmrg } 66135c4bbdfSmrg /* we're in range with the initial velocity, 66235c4bbdfSmrg * so this result is likely better 66335c4bbdfSmrg * (it contains more information). */ 66435c4bbdfSmrg result = tracker_velocity; 66535c4bbdfSmrg used_offset = offset; 66635c4bbdfSmrg } 6676747b715Smrg } 66835c4bbdfSmrg if (offset == vel->num_tracker) { 66935c4bbdfSmrg DebugAccelF("query: last tracker in effect\n"); 67035c4bbdfSmrg used_offset = vel->num_tracker - 1; 6716747b715Smrg } 67235c4bbdfSmrg if (used_offset >= 0) { 67335c4bbdfSmrg#ifdef PTRACCEL_DEBUGGING 67435c4bbdfSmrg MotionTracker *tracker = TRACKER(vel, used_offset); 67535c4bbdfSmrg 67635c4bbdfSmrg DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n", 67735c4bbdfSmrg used_offset, tracker->dx, tracker->dy, 67835c4bbdfSmrg cur_t - tracker->time); 67935c4bbdfSmrg#endif 6804642e01fSmrg } 68135c4bbdfSmrg return result; 6826747b715Smrg} 6834642e01fSmrg 6846747b715Smrg#undef TRACKER_INDEX 68535c4bbdfSmrg#undef TRACKER 6864642e01fSmrg 6876747b715Smrg/** 6886747b715Smrg * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta). 6896747b715Smrg * return true if non-visible state reset is suggested 6906747b715Smrg */ 69135c4bbdfSmrgBOOL 69235c4bbdfSmrgProcessVelocityData2D(DeviceVelocityPtr vel, double dx, double dy, int time) 6936747b715Smrg{ 69435c4bbdfSmrg double velocity; 6954642e01fSmrg 6966747b715Smrg vel->last_velocity = vel->velocity; 6976747b715Smrg 6986747b715Smrg FeedTrackers(vel, dx, dy, time); 6994642e01fSmrg 7006747b715Smrg velocity = QueryTrackers(vel, time); 7016747b715Smrg 70235c4bbdfSmrg DebugAccelF("velocity is %f\n", velocity); 70335c4bbdfSmrg 7046747b715Smrg vel->velocity = velocity; 7056747b715Smrg return velocity == 0; 7066747b715Smrg} 7074642e01fSmrg 7084642e01fSmrg/** 7094642e01fSmrg * this flattens significant ( > 1) mickeys a little bit for more steady 7104642e01fSmrg * constant-velocity response 7114642e01fSmrg */ 71235c4bbdfSmrgstatic inline double 71335c4bbdfSmrgApplySimpleSoftening(double prev_delta, double delta) 7144642e01fSmrg{ 71535c4bbdfSmrg double result = delta; 7164642e01fSmrg 71735c4bbdfSmrg if (delta < -1.0 || delta > 1.0) { 71835c4bbdfSmrg if (delta > prev_delta) 71935c4bbdfSmrg result -= 0.5; 72035c4bbdfSmrg else if (delta < prev_delta) 72135c4bbdfSmrg result += 0.5; 72235c4bbdfSmrg } 72335c4bbdfSmrg return result; 72435c4bbdfSmrg} 7254642e01fSmrg 72635c4bbdfSmrg/** 72735c4bbdfSmrg * Soften the delta based on previous deltas stored in vel. 72835c4bbdfSmrg * 72935c4bbdfSmrg * @param[in,out] fdx Delta X, modified in-place. 73035c4bbdfSmrg * @param[in,out] fdx Delta Y, modified in-place. 73135c4bbdfSmrg */ 7324642e01fSmrgstatic void 73335c4bbdfSmrgApplySoftening(DeviceVelocityPtr vel, double *fdx, double *fdy) 7344642e01fSmrg{ 73535c4bbdfSmrg if (vel->use_softening) { 73635c4bbdfSmrg *fdx = ApplySimpleSoftening(vel->last_dx, *fdx); 73735c4bbdfSmrg *fdy = ApplySimpleSoftening(vel->last_dy, *fdy); 7384642e01fSmrg } 73935c4bbdfSmrg} 7404642e01fSmrg 74135c4bbdfSmrgstatic void 74235c4bbdfSmrgApplyConstantDeceleration(DeviceVelocityPtr vel, double *fdx, double *fdy) 74335c4bbdfSmrg{ 7446747b715Smrg *fdx *= vel->const_acceleration; 7456747b715Smrg *fdy *= vel->const_acceleration; 7464642e01fSmrg} 7474642e01fSmrg 7484642e01fSmrg/* 74935c4bbdfSmrg * compute the acceleration for given velocity and enforce min_acceleration 7504642e01fSmrg */ 75135c4bbdfSmrgdouble 75235c4bbdfSmrgBasicComputeAcceleration(DeviceIntPtr dev, 75335c4bbdfSmrg DeviceVelocityPtr vel, 75435c4bbdfSmrg double velocity, double threshold, double acc) 75535c4bbdfSmrg{ 75635c4bbdfSmrg 75735c4bbdfSmrg double result; 75835c4bbdfSmrg 7596747b715Smrg result = vel->Profile(dev, vel, velocity, threshold, acc); 7604642e01fSmrg 7614642e01fSmrg /* enforce min_acceleration */ 7626747b715Smrg if (result < vel->min_acceleration) 76335c4bbdfSmrg result = vel->min_acceleration; 7644642e01fSmrg return result; 7654642e01fSmrg} 7664642e01fSmrg 7674642e01fSmrg/** 7684642e01fSmrg * Compute acceleration. Takes into account averaging, nv-reset, etc. 76935c4bbdfSmrg * If the velocity has changed, an average is taken of 6 velocity factors: 77035c4bbdfSmrg * current velocity, last velocity and 4 times the average between the two. 7714642e01fSmrg */ 77235c4bbdfSmrgstatic double 77335c4bbdfSmrgComputeAcceleration(DeviceIntPtr dev, 77435c4bbdfSmrg DeviceVelocityPtr vel, double threshold, double acc) 77535c4bbdfSmrg{ 77635c4bbdfSmrg double result; 77735c4bbdfSmrg 77835c4bbdfSmrg if (vel->velocity <= 0) { 77935c4bbdfSmrg DebugAccelF("profile skipped\n"); 7804642e01fSmrg /* 7816747b715Smrg * If we have no idea about device velocity, don't pretend it. 7824642e01fSmrg */ 78335c4bbdfSmrg return 1; 7844642e01fSmrg } 7854642e01fSmrg 78635c4bbdfSmrg if (vel->average_accel && vel->velocity != vel->last_velocity) { 78735c4bbdfSmrg /* use simpson's rule to average acceleration between 78835c4bbdfSmrg * current and previous velocity. 78935c4bbdfSmrg * Though being the more natural choice, it causes a minor delay 79035c4bbdfSmrg * in comparison, so it can be disabled. */ 79135c4bbdfSmrg result = 79235c4bbdfSmrg BasicComputeAcceleration(dev, vel, vel->velocity, threshold, acc); 79335c4bbdfSmrg result += 79435c4bbdfSmrg BasicComputeAcceleration(dev, vel, vel->last_velocity, threshold, 79535c4bbdfSmrg acc); 79635c4bbdfSmrg result += 79735c4bbdfSmrg 4.0f * BasicComputeAcceleration(dev, vel, 79835c4bbdfSmrg (vel->last_velocity + 79935c4bbdfSmrg vel->velocity) / 2, 80035c4bbdfSmrg threshold, 80135c4bbdfSmrg acc); 80235c4bbdfSmrg result /= 6.0f; 80335c4bbdfSmrg DebugAccelF("profile average [%.2f ... %.2f] is %.3f\n", 80435c4bbdfSmrg vel->velocity, vel->last_velocity, result); 80535c4bbdfSmrg } 80635c4bbdfSmrg else { 80735c4bbdfSmrg result = BasicComputeAcceleration(dev, vel, 80835c4bbdfSmrg vel->velocity, threshold, acc); 80935c4bbdfSmrg DebugAccelF("profile sample [%.2f] is %.3f\n", 81035c4bbdfSmrg vel->velocity, result); 8114642e01fSmrg } 8124642e01fSmrg 81335c4bbdfSmrg return result; 81435c4bbdfSmrg} 8154642e01fSmrg 8164642e01fSmrg/***************************************** 8174642e01fSmrg * Acceleration functions and profiles 8184642e01fSmrg ****************************************/ 8194642e01fSmrg 8204642e01fSmrg/** 8214642e01fSmrg * Polynomial function similar previous one, but with f(1) = 1 8224642e01fSmrg */ 82335c4bbdfSmrgstatic double 82435c4bbdfSmrgPolynomialAccelerationProfile(DeviceIntPtr dev, 82535c4bbdfSmrg DeviceVelocityPtr vel, 82635c4bbdfSmrg double velocity, double ignored, double acc) 8274642e01fSmrg{ 82835c4bbdfSmrg return pow(velocity, (acc - 1.0) * 0.5); 8294642e01fSmrg} 8304642e01fSmrg 8314642e01fSmrg/** 8324642e01fSmrg * returns acceleration for velocity. 8334642e01fSmrg * This profile selects the two functions like the old scheme did 8344642e01fSmrg */ 83535c4bbdfSmrgstatic double 83635c4bbdfSmrgClassicProfile(DeviceIntPtr dev, 83735c4bbdfSmrg DeviceVelocityPtr vel, 83835c4bbdfSmrg double velocity, double threshold, double acc) 8394642e01fSmrg{ 8406747b715Smrg if (threshold > 0) { 84135c4bbdfSmrg return SimpleSmoothProfile(dev, vel, velocity, threshold, acc); 84235c4bbdfSmrg } 84335c4bbdfSmrg else { 84435c4bbdfSmrg return PolynomialAccelerationProfile(dev, vel, velocity, 0, acc); 8454642e01fSmrg } 8464642e01fSmrg} 8474642e01fSmrg 8484642e01fSmrg/** 8494642e01fSmrg * Power profile 8504642e01fSmrg * This has a completely smooth transition curve, i.e. no jumps in the 8514642e01fSmrg * derivatives. 8524642e01fSmrg * 8534642e01fSmrg * This has the expense of overall response dependency on min-acceleration. 8544642e01fSmrg * In effect, min_acceleration mimics const_acceleration in this profile. 8554642e01fSmrg */ 85635c4bbdfSmrgstatic double 85735c4bbdfSmrgPowerProfile(DeviceIntPtr dev, 85835c4bbdfSmrg DeviceVelocityPtr vel, 85935c4bbdfSmrg double velocity, double threshold, double acc) 8604642e01fSmrg{ 86135c4bbdfSmrg double vel_dist; 8624642e01fSmrg 86335c4bbdfSmrg acc = (acc - 1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */ 8644642e01fSmrg 8654642e01fSmrg if (velocity <= threshold) 8666747b715Smrg return vel->min_acceleration; 8674642e01fSmrg vel_dist = velocity - threshold; 8686747b715Smrg return (pow(acc, vel_dist)) * vel->min_acceleration; 8694642e01fSmrg} 8704642e01fSmrg 8714642e01fSmrg/** 8724642e01fSmrg * just a smooth function in [0..1] -> [0..1] 8734642e01fSmrg * - point symmetry at 0.5 8744642e01fSmrg * - f'(0) = f'(1) = 0 8754642e01fSmrg * - starts faster than a sinoid 8764642e01fSmrg * - smoothness C1 (Cinf if you dare to ignore endpoints) 8774642e01fSmrg */ 87835c4bbdfSmrgstatic inline double 87935c4bbdfSmrgCalcPenumbralGradient(double x) 88035c4bbdfSmrg{ 8814642e01fSmrg x *= 2.0f; 8824642e01fSmrg x -= 1.0f; 88335c4bbdfSmrg return 0.5f + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI; 8844642e01fSmrg} 8854642e01fSmrg 8864642e01fSmrg/** 8874642e01fSmrg * acceleration function similar to classic accelerated/unaccelerated, 8884642e01fSmrg * but with smooth transition in between (and towards zero for adaptive dec.). 8894642e01fSmrg */ 89035c4bbdfSmrgstatic double 89135c4bbdfSmrgSimpleSmoothProfile(DeviceIntPtr dev, 89235c4bbdfSmrg DeviceVelocityPtr vel, 89335c4bbdfSmrg double velocity, double threshold, double acc) 8944642e01fSmrg{ 89535c4bbdfSmrg if (velocity < 1.0f) 89635c4bbdfSmrg return CalcPenumbralGradient(0.5 + velocity * 0.5) * 2.0f - 1.0f; 89735c4bbdfSmrg if (threshold < 1.0f) 8984642e01fSmrg threshold = 1.0f; 8994642e01fSmrg if (velocity <= threshold) 9004642e01fSmrg return 1; 9014642e01fSmrg velocity /= threshold; 9024642e01fSmrg if (velocity >= acc) 9034642e01fSmrg return acc; 9044642e01fSmrg else 90535c4bbdfSmrg return 1.0f + (CalcPenumbralGradient(velocity / acc) * (acc - 1.0f)); 9064642e01fSmrg} 9074642e01fSmrg 9084642e01fSmrg/** 9094642e01fSmrg * This profile uses the first half of the penumbral gradient as a start 9104642e01fSmrg * and then scales linearly. 9114642e01fSmrg */ 91235c4bbdfSmrgstatic double 91335c4bbdfSmrgSmoothLinearProfile(DeviceIntPtr dev, 91435c4bbdfSmrg DeviceVelocityPtr vel, 91535c4bbdfSmrg double velocity, double threshold, double acc) 9164642e01fSmrg{ 91735c4bbdfSmrg double res, nv; 9184642e01fSmrg 91935c4bbdfSmrg if (acc > 1.0f) 92035c4bbdfSmrg acc -= 1.0f; /*this is so acc = 1 is no acceleration */ 9214642e01fSmrg else 9224642e01fSmrg return 1.0f; 9234642e01fSmrg 9244642e01fSmrg nv = (velocity - threshold) * acc * 0.5f; 9254642e01fSmrg 92635c4bbdfSmrg if (nv < 0) { 9274642e01fSmrg res = 0; 92835c4bbdfSmrg } 92935c4bbdfSmrg else if (nv < 2) { 93035c4bbdfSmrg res = CalcPenumbralGradient(nv * 0.25f) * 2.0f; 93135c4bbdfSmrg } 93235c4bbdfSmrg else { 9334642e01fSmrg nv -= 2.0f; 9344642e01fSmrg res = nv * 2.0f / M_PI /* steepness of gradient at 0.5 */ 93535c4bbdfSmrg + 1.0f; /* gradient crosses 2|1 */ 9364642e01fSmrg } 9376747b715Smrg res += vel->min_acceleration; 9384642e01fSmrg return res; 9394642e01fSmrg} 9404642e01fSmrg 9416747b715Smrg/** 9426747b715Smrg * From 0 to threshold, the response graduates smoothly from min_accel to 9436747b715Smrg * acceleration. Beyond threshold it is exactly the specified acceleration. 9446747b715Smrg */ 94535c4bbdfSmrgstatic double 94635c4bbdfSmrgSmoothLimitedProfile(DeviceIntPtr dev, 94735c4bbdfSmrg DeviceVelocityPtr vel, 94835c4bbdfSmrg double velocity, double threshold, double acc) 9496747b715Smrg{ 95035c4bbdfSmrg double res; 9516747b715Smrg 95235c4bbdfSmrg if (velocity >= threshold || threshold == 0.0f) 95335c4bbdfSmrg return acc; 9546747b715Smrg 95535c4bbdfSmrg velocity /= threshold; /* should be [0..1[ now */ 9566747b715Smrg 9576747b715Smrg res = CalcPenumbralGradient(velocity) * (acc - vel->min_acceleration); 9586747b715Smrg 9596747b715Smrg return vel->min_acceleration + res; 9606747b715Smrg} 9616747b715Smrg 96235c4bbdfSmrgstatic double 96335c4bbdfSmrgLinearProfile(DeviceIntPtr dev, 96435c4bbdfSmrg DeviceVelocityPtr vel, 96535c4bbdfSmrg double velocity, double threshold, double acc) 9664642e01fSmrg{ 9674642e01fSmrg return acc * velocity; 9684642e01fSmrg} 9694642e01fSmrg 97035c4bbdfSmrgstatic double 97135c4bbdfSmrgNoProfile(DeviceIntPtr dev, 97235c4bbdfSmrg DeviceVelocityPtr vel, double velocity, double threshold, double acc) 9736747b715Smrg{ 9746747b715Smrg return 1.0f; 9756747b715Smrg} 9764642e01fSmrg 9776747b715Smrgstatic PointerAccelerationProfileFunc 97835c4bbdfSmrgGetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) 9794642e01fSmrg{ 98035c4bbdfSmrg switch (profile_num) { 98135c4bbdfSmrg case AccelProfileClassic: 98235c4bbdfSmrg return ClassicProfile; 98335c4bbdfSmrg case AccelProfileDeviceSpecific: 98435c4bbdfSmrg return vel->deviceSpecificProfile; 98535c4bbdfSmrg case AccelProfilePolynomial: 98635c4bbdfSmrg return PolynomialAccelerationProfile; 98735c4bbdfSmrg case AccelProfileSmoothLinear: 98835c4bbdfSmrg return SmoothLinearProfile; 98935c4bbdfSmrg case AccelProfileSimple: 99035c4bbdfSmrg return SimpleSmoothProfile; 99135c4bbdfSmrg case AccelProfilePower: 99235c4bbdfSmrg return PowerProfile; 99335c4bbdfSmrg case AccelProfileLinear: 99435c4bbdfSmrg return LinearProfile; 99535c4bbdfSmrg case AccelProfileSmoothLimited: 99635c4bbdfSmrg return SmoothLimitedProfile; 99735c4bbdfSmrg case AccelProfileNone: 99835c4bbdfSmrg return NoProfile; 99935c4bbdfSmrg default: 100035c4bbdfSmrg return NULL; 10014642e01fSmrg } 10026747b715Smrg} 10036747b715Smrg 10046747b715Smrg/** 10056747b715Smrg * Set the profile by number. 10066747b715Smrg * Intended to make profiles exchangeable at runtime. 10076747b715Smrg * If you created a profile, give it a number here and in the header to 10086747b715Smrg * make it selectable. In case some profile-specific init is needed, here 10096747b715Smrg * would be a good place, since FreeVelocityData() also calls this with 10106747b715Smrg * PROFILE_UNINITIALIZE. 10116747b715Smrg * 10126747b715Smrg * returns FALSE if profile number is unavailable, TRUE otherwise. 10136747b715Smrg */ 10146747b715Smrgint 101535c4bbdfSmrgSetAccelerationProfile(DeviceVelocityPtr vel, int profile_num) 10166747b715Smrg{ 10176747b715Smrg PointerAccelerationProfileFunc profile; 101835c4bbdfSmrg 10196747b715Smrg profile = GetAccelerationProfile(vel, profile_num); 10206747b715Smrg 102135c4bbdfSmrg if (profile == NULL && profile_num != PROFILE_UNINITIALIZE) 102235c4bbdfSmrg return FALSE; 10236747b715Smrg 10249ace9065Smrg /* Here one could free old profile-private data */ 10259ace9065Smrg free(vel->profile_private); 10269ace9065Smrg vel->profile_private = NULL; 10274642e01fSmrg /* Here one could init profile-private data */ 10286747b715Smrg vel->Profile = profile; 10296747b715Smrg vel->statistics.profile_number = profile_num; 10304642e01fSmrg return TRUE; 10314642e01fSmrg} 10324642e01fSmrg 10334642e01fSmrg/********************************************** 10344642e01fSmrg * driver interaction 10354642e01fSmrg **********************************************/ 10364642e01fSmrg 10374642e01fSmrg/** 10384642e01fSmrg * device-specific profile 10394642e01fSmrg * 10404642e01fSmrg * The device-specific profile is intended as a hook for a driver 10414642e01fSmrg * which may want to provide an own acceleration profile. 10424642e01fSmrg * It should not rely on profile-private data, instead 10434642e01fSmrg * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends). 10444642e01fSmrg * Users may override or choose it. 10454642e01fSmrg */ 10466747b715Smrgvoid 104735c4bbdfSmrgSetDeviceSpecificAccelerationProfile(DeviceVelocityPtr vel, 104835c4bbdfSmrg PointerAccelerationProfileFunc profile) 10494642e01fSmrg{ 105035c4bbdfSmrg if (vel) 105135c4bbdfSmrg vel->deviceSpecificProfile = profile; 10524642e01fSmrg} 10534642e01fSmrg 10544642e01fSmrg/** 10554642e01fSmrg * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if 10564642e01fSmrg * the predictable acceleration scheme is not in effect. 10574642e01fSmrg */ 10586747b715SmrgDeviceVelocityPtr 105935c4bbdfSmrgGetDevicePredictableAccelData(DeviceIntPtr dev) 10604642e01fSmrg{ 106135c4bbdfSmrg BUG_RETURN_VAL(!dev, NULL); 10624642e01fSmrg 106335c4bbdfSmrg if (dev->valuator && 106435c4bbdfSmrg dev->valuator->accelScheme.AccelSchemeProc == 106535c4bbdfSmrg acceleratePointerPredictable && 106635c4bbdfSmrg dev->valuator->accelScheme.accelData != NULL) { 106735c4bbdfSmrg 106835c4bbdfSmrg return ((PredictableAccelSchemePtr) 106935c4bbdfSmrg dev->valuator->accelScheme.accelData)->vel; 10704642e01fSmrg } 10714642e01fSmrg return NULL; 10724642e01fSmrg} 10734642e01fSmrg 10744642e01fSmrg/******************************** 10754642e01fSmrg * acceleration schemes 10764642e01fSmrg *******************************/ 10774642e01fSmrg 10784642e01fSmrg/** 10794642e01fSmrg * Modifies valuators in-place. 10804642e01fSmrg * This version employs a velocity approximation algorithm to 10814642e01fSmrg * enable fine-grained predictable acceleration profiles. 10824642e01fSmrg */ 10834642e01fSmrgvoid 108435c4bbdfSmrgacceleratePointerPredictable(DeviceIntPtr dev, ValuatorMask *val, CARD32 evtime) 10854642e01fSmrg{ 108635c4bbdfSmrg double dx = 0, dy = 0; 108735c4bbdfSmrg DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev); 10886747b715Smrg Bool soften = TRUE; 10894642e01fSmrg 109035c4bbdfSmrg if (valuator_mask_num_valuators(val) == 0 || !velocitydata) 10914642e01fSmrg return; 10924642e01fSmrg 10936747b715Smrg if (velocitydata->statistics.profile_number == AccelProfileNone && 109435c4bbdfSmrg velocitydata->const_acceleration == 1.0f) { 109535c4bbdfSmrg return; /*we're inactive anyway, so skip the whole thing. */ 10966747b715Smrg } 10976747b715Smrg 109835c4bbdfSmrg if (valuator_mask_isset(val, 0)) { 109935c4bbdfSmrg dx = valuator_mask_get_double(val, 0); 11004642e01fSmrg } 110135c4bbdfSmrg 110235c4bbdfSmrg if (valuator_mask_isset(val, 1)) { 110335c4bbdfSmrg dy = valuator_mask_get_double(val, 1); 11044642e01fSmrg } 11054642e01fSmrg 110635c4bbdfSmrg if (dx != 0.0 || dy != 0.0) { 11076747b715Smrg /* reset non-visible state? */ 110835c4bbdfSmrg if (ProcessVelocityData2D(velocitydata, dx, dy, evtime)) { 11096747b715Smrg soften = FALSE; 11104642e01fSmrg } 11114642e01fSmrg 11126747b715Smrg if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { 111335c4bbdfSmrg double mult; 111435c4bbdfSmrg 11154642e01fSmrg /* invoke acceleration profile to determine acceleration */ 111635c4bbdfSmrg mult = ComputeAcceleration(dev, velocitydata, 111735c4bbdfSmrg dev->ptrfeed->ctrl.threshold, 111835c4bbdfSmrg (double) dev->ptrfeed->ctrl.num / 111935c4bbdfSmrg (double) dev->ptrfeed->ctrl.den); 112035c4bbdfSmrg 112135c4bbdfSmrg DebugAccelF("mult is %f\n", mult); 112235c4bbdfSmrg if (mult != 1.0f || velocitydata->const_acceleration != 1.0f) { 112335c4bbdfSmrg if (mult > 1.0f && soften) 112435c4bbdfSmrg ApplySoftening(velocitydata, &dx, &dy); 112535c4bbdfSmrg ApplyConstantDeceleration(velocitydata, &dx, &dy); 112635c4bbdfSmrg 112735c4bbdfSmrg if (dx != 0.0) 112835c4bbdfSmrg valuator_mask_set_double(val, 0, mult * dx); 112935c4bbdfSmrg if (dy != 0.0) 113035c4bbdfSmrg valuator_mask_set_double(val, 1, mult * dy); 113135c4bbdfSmrg DebugAccelF("delta x:%.3f y:%.3f\n", mult * dx, mult * dy); 11324642e01fSmrg } 11334642e01fSmrg } 11344642e01fSmrg } 11354642e01fSmrg /* remember last motion delta (for softening/slow movement treatment) */ 11364642e01fSmrg velocitydata->last_dx = dx; 11374642e01fSmrg velocitydata->last_dy = dy; 11384642e01fSmrg} 11394642e01fSmrg 11404642e01fSmrg/** 11414642e01fSmrg * Originally a part of xf86PostMotionEvent; modifies valuators 11424642e01fSmrg * in-place. Retained mostly for embedded scenarios. 11434642e01fSmrg */ 11444642e01fSmrgvoid 114535c4bbdfSmrgacceleratePointerLightweight(DeviceIntPtr dev, 114635c4bbdfSmrg ValuatorMask *val, CARD32 ignored) 11474642e01fSmrg{ 114835c4bbdfSmrg double mult = 0.0, tmpf; 114935c4bbdfSmrg double dx = 0.0, dy = 0.0; 11504642e01fSmrg 115135c4bbdfSmrg if (valuator_mask_isset(val, 0)) { 115235c4bbdfSmrg dx = valuator_mask_get(val, 0); 11534642e01fSmrg } 115435c4bbdfSmrg 115535c4bbdfSmrg if (valuator_mask_isset(val, 1)) { 115635c4bbdfSmrg dy = valuator_mask_get(val, 1); 11574642e01fSmrg } 11584642e01fSmrg 115935c4bbdfSmrg if (valuator_mask_num_valuators(val) == 0) 11604642e01fSmrg return; 11614642e01fSmrg 11626747b715Smrg if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { 11634642e01fSmrg /* modeled from xf86Events.c */ 11646747b715Smrg if (dev->ptrfeed->ctrl.threshold) { 116535c4bbdfSmrg if ((fabs(dx) + fabs(dy)) >= dev->ptrfeed->ctrl.threshold) { 116635c4bbdfSmrg if (dx != 0.0) { 116735c4bbdfSmrg tmpf = (dx * (double) (dev->ptrfeed->ctrl.num)) / 116835c4bbdfSmrg (double) (dev->ptrfeed->ctrl.den); 116935c4bbdfSmrg valuator_mask_set_double(val, 0, tmpf); 11704642e01fSmrg } 11714642e01fSmrg 117235c4bbdfSmrg if (dy != 0.0) { 117335c4bbdfSmrg tmpf = (dy * (double) (dev->ptrfeed->ctrl.num)) / 117435c4bbdfSmrg (double) (dev->ptrfeed->ctrl.den); 117535c4bbdfSmrg valuator_mask_set_double(val, 1, tmpf); 11764642e01fSmrg } 11774642e01fSmrg } 11784642e01fSmrg } 11794642e01fSmrg else { 118035c4bbdfSmrg mult = pow(dx * dx + dy * dy, 118135c4bbdfSmrg ((double) (dev->ptrfeed->ctrl.num) / 118235c4bbdfSmrg (double) (dev->ptrfeed->ctrl.den) - 1.0) / 2.0) / 2.0; 118335c4bbdfSmrg if (dx != 0.0) 118435c4bbdfSmrg valuator_mask_set_double(val, 0, mult * dx); 118535c4bbdfSmrg if (dy != 0.0) 118635c4bbdfSmrg valuator_mask_set_double(val, 1, mult * dy); 11874642e01fSmrg } 11884642e01fSmrg } 11894642e01fSmrg} 1190