946 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			946 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  *
 | |
|  * Copyright © 2006-2008 Simon Thum             simon dot thum at gmx dot de
 | |
|  *
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a
 | |
|  * copy of this software and associated documentation files (the "Software"),
 | |
|  * to deal in the Software without restriction, including without limitation
 | |
|  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 | |
|  * and/or sell copies of the Software, and to permit persons to whom the
 | |
|  * Software is furnished to do so, subject to the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice (including the next
 | |
|  * paragraph) shall be included in all copies or substantial portions of the
 | |
|  * Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 | |
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | |
|  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | |
|  * DEALINGS IN THE SOFTWARE.
 | |
|  */
 | |
| 
 | |
| #ifdef HAVE_DIX_CONFIG_H
 | |
| #include <dix-config.h>
 | |
| #endif
 | |
| 
 | |
| #include <math.h>
 | |
| #include <ptrveloc.h>
 | |
| #include <inputstr.h>
 | |
| #include <assert.h>
 | |
| #include <os.h>
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * Predictable pointer ballistics
 | |
|  *
 | |
|  * 2006-2008 by Simon Thum (simon [dot] thum [at] gmx de)
 | |
|  *
 | |
|  * Serves 3 complementary functions:
 | |
|  * 1) provide a sophisticated ballistic velocity estimate to improve
 | |
|  *    the relation between velocity (of the device) and acceleration
 | |
|  * 2) make arbitrary acceleration profiles possible
 | |
|  * 3) decelerate by two means (constant and adaptive) if enabled
 | |
|  *
 | |
|  * Important concepts are the
 | |
|  *
 | |
|  * - Scheme
 | |
|  *      which selects the basic algorithm
 | |
|  *      (see devices.c/InitPointerAccelerationScheme)
 | |
|  * - Profile
 | |
|  *      which returns an acceleration
 | |
|  *      for a given velocity
 | |
|  *
 | |
|  *  The profile can be selected by the user (potentially at runtime).
 | |
|  *  the classic profile is intended to cleanly perform old-style
 | |
|  *  function selection (threshold =/!= 0)
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| 
 | |
| /* fwds */
 | |
| static inline void
 | |
| FeedFilterStage(FilterStagePtr s, float value, int tdiff);
 | |
| extern void
 | |
| InitFilterStage(FilterStagePtr s, float rdecay, int lutsize);
 | |
| void
 | |
| CleanupFilterChain(DeviceVelocityPtr s);
 | |
| int
 | |
| SetAccelerationProfile(DeviceVelocityPtr s, int profile_num);
 | |
| void
 | |
| InitFilterChain(DeviceVelocityPtr s, float rdecay, float degression,
 | |
|                 int stages, int lutsize);
 | |
| void
 | |
| CleanupFilterChain(DeviceVelocityPtr s);
 | |
| static float
 | |
| SimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity,
 | |
|                     float threshold, float acc);
 | |
| 
 | |
| 
 | |
| 
 | |
| /*#define PTRACCEL_DEBUGGING*/
 | |
| 
 | |
| #ifdef PTRACCEL_DEBUGGING
 | |
| #define DebugAccelF ErrorF
 | |
| #else
 | |
| #define DebugAccelF(...) /* */
 | |
| #endif
 | |
| 
 | |
| /********************************
 | |
|  *  Init/Uninit etc
 | |
|  *******************************/
 | |
| 
 | |
| /**
 | |
|  * Init struct so it should match the average case
 | |
|  */
 | |
| void
 | |
| InitVelocityData(DeviceVelocityPtr s)
 | |
| {
 | |
|     s->lrm_time = 0;
 | |
|     s->velocity  = 0;
 | |
|     s->last_velocity = 0;
 | |
|     s->corr_mul = 10.0;      /* dots per 10 milisecond should be usable */
 | |
|     s->const_acceleration = 1.0;   /* no acceleration/deceleration  */
 | |
|     s->reset_time = 300;
 | |
|     s->last_reset = FALSE;
 | |
|     s->last_dx = 0;
 | |
|     s->last_dy = 0;
 | |
|     s->use_softening = 1;
 | |
|     s->min_acceleration = 1.0; /* don't decelerate */
 | |
|     s->coupling = 0.25;
 | |
|     s->average_accel = TRUE;
 | |
|     s->profile_private = NULL;
 | |
|     memset(&s->statistics, 0, sizeof(s->statistics));
 | |
|     memset(&s->filters, 0, sizeof(s->filters));
 | |
|     SetAccelerationProfile(s, AccelProfileClassic);
 | |
|     InitFilterChain(s, (float)1.0/20.0, 1, 1, 40);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Clean up
 | |
|  */
 | |
| static void
 | |
| FreeVelocityData(DeviceVelocityPtr s){
 | |
|     CleanupFilterChain(s);
 | |
|     SetAccelerationProfile(s, -1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  *  dix uninit helper, called through scheme
 | |
|  */
 | |
| void
 | |
| AccelerationDefaultCleanup(DeviceIntPtr pDev){
 | |
|     /*sanity check*/
 | |
|     if( pDev->valuator->accelScheme.AccelSchemeProc == acceleratePointerPredictable
 | |
|             && pDev->valuator->accelScheme.accelData != NULL){
 | |
|         pDev->valuator->accelScheme.AccelSchemeProc = NULL;
 | |
|         FreeVelocityData(pDev->valuator->accelScheme.accelData);
 | |
|         xfree(pDev->valuator->accelScheme.accelData);
 | |
|         pDev->valuator->accelScheme.accelData = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*********************
 | |
|  * Filtering logic
 | |
|  ********************/
 | |
| 
 | |
| /**
 | |
| Initialize a filter chain.
 | |
| Expected result is a series of filters, each progressively more integrating.
 | |
| 
 | |
| This allows for two strategies: Either you have one filter which is reasonable
 | |
| and is being coupled to account for fast-changing input, or you have 'one for
 | |
| every situation'. You might want to have tighter coupling then, e.g. 0.1.
 | |
| In the filter stats, you can see if a reasonable filter useage emerges.
 | |
| */
 | |
| void
 | |
| InitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize)
 | |
| {
 | |
|     int fn;
 | |
|     if((stages > 1 && progression < 1.0f) || 0 == progression){
 | |
| 	ErrorF("(dix ptracc) invalid filter chain progression specified\n");
 | |
| 	return;
 | |
|     }
 | |
|     /* Block here to support runtime filter adjustment */
 | |
|     OsBlockSignals();
 | |
|     for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
 | |
| 	if(fn < stages){
 | |
| 	    InitFilterStage(&s->filters[fn], rdecay, lutsize);
 | |
| 	}else{
 | |
| 	    InitFilterStage(&s->filters[fn], 0, 0);
 | |
| 	}
 | |
| 	rdecay /= progression;
 | |
|     }
 | |
|     /* release again. Should the input loop be threaded, we also need
 | |
|      * memory release here (in principle).
 | |
|      */
 | |
|     OsReleaseSignals();
 | |
| }
 | |
| 
 | |
| 
 | |
| void
 | |
| CleanupFilterChain(DeviceVelocityPtr s)
 | |
| {
 | |
|     int fn;
 | |
| 
 | |
|     for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++)
 | |
| 	InitFilterStage(&s->filters[fn], 0, 0);
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| StuffFilterChain(DeviceVelocityPtr s, float value)
 | |
| {
 | |
|     int fn;
 | |
| 
 | |
|     for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
 | |
| 	if(s->filters[fn].rdecay != 0)
 | |
| 	    s->filters[fn].current = value;
 | |
| 	else break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Adjust weighting decay and lut for a stage
 | |
|  * The weight fn is designed so its integral 0->inf is unity, so we end
 | |
|  * up with a stable (basically IIR) filter. It always draws
 | |
|  * towards its more current input values, which have more weight the older
 | |
|  * the last input value is.
 | |
|  */
 | |
| void
 | |
| InitFilterStage(FilterStagePtr s, float rdecay, int lutsize)
 | |
| {
 | |
|     int x;
 | |
|     float *newlut;
 | |
|     float *oldlut;
 | |
| 
 | |
|     s->fading_lut_size  = 0; /* prevent access */
 | |
| 
 | |
|     if(lutsize > 0){
 | |
|         newlut = xalloc (sizeof(float)* lutsize);
 | |
|         if(!newlut)
 | |
|             return;
 | |
|         for(x = 0; x < lutsize; x++)
 | |
|             newlut[x] = pow(0.5, ((float)x) * rdecay);
 | |
|     }else{
 | |
|         newlut = NULL;
 | |
|     }
 | |
|     oldlut = s->fading_lut;
 | |
|     s->fading_lut = newlut;
 | |
|     s->rdecay = rdecay;
 | |
|     s->fading_lut_size = lutsize;
 | |
|     s->current = 0;
 | |
|     if(oldlut != NULL)
 | |
|         xfree(oldlut);
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline void
 | |
| FeedFilterChain(DeviceVelocityPtr s, float value, int tdiff)
 | |
| {
 | |
|     int fn;
 | |
| 
 | |
|     for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
 | |
| 	if(s->filters[fn].rdecay != 0)
 | |
| 	    FeedFilterStage(&s->filters[fn], value, tdiff);
 | |
| 	else break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline void
 | |
| FeedFilterStage(FilterStagePtr s, float value, int tdiff){
 | |
|     float fade;
 | |
|     if(tdiff < s->fading_lut_size)
 | |
|         fade = s->fading_lut[tdiff];
 | |
|     else
 | |
|         fade = pow(0.5, ((float)tdiff) * s->rdecay);
 | |
|     s->current *= fade;    /* fade out old velocity */
 | |
|     s->current += value * (1.0f - fade);    /* and add up current */
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Select the most filtered matching result. Also, the first
 | |
|  * mismatching filter may be set to value (coupling).
 | |
|  */
 | |
| static inline float
 | |
| QueryFilterChain(
 | |
|     DeviceVelocityPtr s,
 | |
|     float value)
 | |
| {
 | |
|     int fn, rfn = 0, cfn = -1;
 | |
|     float cur, result = value;
 | |
| 
 | |
|     /* try to retrieve most integrated result 'within range'
 | |
|      * Assumption: filter are in order least to most integrating */
 | |
|     for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
 | |
| 	if(0.0f == s->filters[fn].rdecay)
 | |
| 	    break;
 | |
| 	cur = s->filters[fn].current;
 | |
| 
 | |
| 	if (fabs(value - cur) <= (s->coupling * (value + cur))){
 | |
| 	    result = cur;
 | |
| 	    rfn = fn + 1; /*remember result determining filter */
 | |
| 	} else if(cfn == -1){
 | |
| 	    cfn = fn; /* remember first mismatching filter */
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     s->statistics.filter_usecount[rfn]++;
 | |
|     DebugAccelF("(dix ptracc) result from stage %i,  input %.2f, output %.2f\n",
 | |
|            rfn, value, result);
 | |
| 
 | |
|     /* override first mismatching current (coupling) so the filter
 | |
|      * catches up quickly. */
 | |
|     if(cfn != -1)
 | |
|         s->filters[cfn].current = result;
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| /********************************
 | |
|  *  velocity computation
 | |
|  *******************************/
 | |
| 
 | |
| /**
 | |
|  * return the axis if mickey is insignificant and axis-aligned,
 | |
|  * -1 otherwise
 | |
|  * 1 for x-axis
 | |
|  * 2 for y-axis
 | |
|  */
 | |
| static inline short
 | |
| GetAxis(int dx, int dy){
 | |
|     if(dx == 0 || dy == 0){
 | |
|         if(dx == 1 || dx == -1)
 | |
|             return 1;
 | |
|         if(dy == 1 || dy == -1)
 | |
|             return 2;
 | |
|         return -1;
 | |
|     }else{
 | |
|         return -1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Perform velocity approximation
 | |
|  * return true if non-visible state reset is suggested
 | |
|  */
 | |
| static short
 | |
| ProcessVelocityData(
 | |
|     DeviceVelocityPtr s,
 | |
|     int dx,
 | |
|     int dy,
 | |
|     int time)
 | |
| {
 | |
|     float cvelocity;
 | |
| 
 | |
|     int diff = time - s->lrm_time;
 | |
|     int cur_ax, last_ax;
 | |
|     short reset = (diff >= s->reset_time);
 | |
| 
 | |
|     /* remember last round's result */
 | |
|     s->last_velocity = s->velocity;
 | |
|     cur_ax = GetAxis(dx, dy);
 | |
|     last_ax = GetAxis(s->last_dx, s->last_dy);
 | |
| 
 | |
|     if(cur_ax != last_ax && cur_ax != -1 && last_ax != -1 && !reset){
 | |
|         /* correct for the error induced when diagonal movements are
 | |
|            reported as alternating axis mickeys */
 | |
|         dx += s->last_dx;
 | |
|         dy += s->last_dy;
 | |
|         diff += s->last_diff;
 | |
|         s->last_diff = time - s->lrm_time; /* prevent repeating add-up */
 | |
|         DebugAccelF("(dix ptracc) axial correction\n");
 | |
|     }else{
 | |
|         s->last_diff = diff;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * cvelocity is not a real velocity yet, more a motion delta. constant
 | |
|      * acceleration is multiplied here to make the velocity an on-screen
 | |
|      * velocity (pix/t as opposed to [insert unit]/t). This is intended to
 | |
|      * make multiple devices with widely varying ConstantDecelerations respond
 | |
|      * similar to acceleration controls.
 | |
|      */
 | |
|     cvelocity = (float)sqrt(dx*dx + dy*dy) * s->const_acceleration;
 | |
| 
 | |
|     s->lrm_time = time;
 | |
| 
 | |
|     if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */
 | |
|         /* simply set velocity from current movement, no reset. */
 | |
|         s->velocity = cvelocity;
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     if (diff == 0)
 | |
|         diff = 1; /* prevent div-by-zero, though it shouldn't happen anyway*/
 | |
| 
 | |
|     /* translate velocity to dots/ms (somewhat intractable in integers,
 | |
|        so we multiply by some per-device adjustable factor) */
 | |
|     cvelocity = cvelocity * s->corr_mul / (float)diff;
 | |
| 
 | |
|     /* short-circuit: when nv-reset the rest can be skipped */
 | |
|     if(reset == TRUE){
 | |
| 	/*
 | |
| 	 * we don't really have a velocity here, since diff includes inactive
 | |
| 	 * time. This is dealt with in ComputeAcceleration.
 | |
| 	 */
 | |
| 	StuffFilterChain(s, cvelocity);
 | |
| 	s->velocity = s->last_velocity = cvelocity;
 | |
| 	s->last_reset = TRUE;
 | |
| 	DebugAccelF("(dix ptracc) non-visible state reset\n");
 | |
| 	return TRUE;
 | |
|     }
 | |
| 
 | |
|     if(s->last_reset == TRUE){
 | |
| 	/*
 | |
| 	 * when here, we're probably processing the second mickey of a starting
 | |
| 	 * stroke. This happens to be the first time we can reasonably pretend
 | |
| 	 * that cvelocity is an actual velocity. Thus, to opt precision, we
 | |
| 	 * stuff that into the filter chain.
 | |
| 	 */
 | |
| 	s->last_reset = FALSE;
 | |
| 	DebugAccelF("(dix ptracc) after-reset vel:%.3f\n", cvelocity);
 | |
| 	StuffFilterChain(s, cvelocity);
 | |
| 	s->velocity = cvelocity;
 | |
| 	return FALSE;
 | |
|     }
 | |
| 
 | |
|     /* feed into filter chain */
 | |
|     FeedFilterChain(s, cvelocity, diff);
 | |
| 
 | |
|     /* perform coupling and decide final value */
 | |
|     s->velocity = QueryFilterChain(s, cvelocity);
 | |
| 
 | |
|     DebugAccelF("(dix ptracc) guess: vel=%.3f diff=%d   %i|%i|%i|%i|%i|%i|%i|%i|%i\n",
 | |
|            s->velocity, diff,
 | |
|            s->statistics.filter_usecount[0], s->statistics.filter_usecount[1],
 | |
|            s->statistics.filter_usecount[2], s->statistics.filter_usecount[3],
 | |
|            s->statistics.filter_usecount[4], s->statistics.filter_usecount[5],
 | |
|            s->statistics.filter_usecount[6], s->statistics.filter_usecount[7],
 | |
|            s->statistics.filter_usecount[8]);
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * this flattens significant ( > 1) mickeys a little bit for more steady
 | |
|  * constant-velocity response
 | |
|  */
 | |
| static inline float
 | |
| ApplySimpleSoftening(int od, int d)
 | |
| {
 | |
|     float res = d;
 | |
|     if (d <= 1 && d >= -1)
 | |
|         return res;
 | |
|     if (d > od)
 | |
|         res -= 0.5;
 | |
|     else if (d < od)
 | |
|         res += 0.5;
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| ApplySofteningAndConstantDeceleration(
 | |
|         DeviceVelocityPtr s,
 | |
|         int dx,
 | |
|         int dy,
 | |
|         float* fdx,
 | |
|         float* fdy,
 | |
|         short do_soften)
 | |
| {
 | |
|     if (do_soften && s->use_softening) {
 | |
|         *fdx = ApplySimpleSoftening(s->last_dx, dx);
 | |
|         *fdy = ApplySimpleSoftening(s->last_dy, dy);
 | |
|     } else {
 | |
|         *fdx = dx;
 | |
|         *fdy = dy;
 | |
|     }
 | |
| 
 | |
|     *fdx *= s->const_acceleration;
 | |
|     *fdy *= s->const_acceleration;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compute the acceleration for given velocity and enforce min_acceleartion
 | |
|  */
 | |
| static float
 | |
| BasicComputeAcceleration(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc){
 | |
| 
 | |
|     float result;
 | |
|     result = pVel->Profile(pVel, velocity, threshold, acc);
 | |
| 
 | |
|     /* enforce min_acceleration */
 | |
|     if (result < pVel->min_acceleration)
 | |
| 	result = pVel->min_acceleration;
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Compute acceleration. Takes into account averaging, nv-reset, etc.
 | |
|  */
 | |
| static float
 | |
| ComputeAcceleration(
 | |
|     DeviceVelocityPtr vel,
 | |
|     float threshold,
 | |
|     float acc){
 | |
|     float res;
 | |
| 
 | |
|     if(vel->last_reset){
 | |
| 	DebugAccelF("(dix ptracc) profile skipped\n");
 | |
|         /*
 | |
|          * This is intended to override the first estimate of a stroke,
 | |
|          * which is too low (see ProcessVelocityData). 1 should make sure
 | |
|          * the mickey is seen on screen.
 | |
|          */
 | |
| 	return 1;
 | |
|     }
 | |
| 
 | |
|     if(vel->average_accel && vel->velocity != vel->last_velocity){
 | |
| 	/* use simpson's rule to average acceleration between
 | |
| 	 * current and previous velocity.
 | |
| 	 * Though being the more natural choice, it causes a minor delay
 | |
| 	 * in comparison, so it can be disabled. */
 | |
| 	res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc);
 | |
| 	res += BasicComputeAcceleration(vel, vel->last_velocity, threshold, acc);
 | |
| 	res += 4.0f * BasicComputeAcceleration(vel,
 | |
| 	                   (vel->last_velocity + vel->velocity) / 2,
 | |
| 	                   threshold, acc);
 | |
| 	res /= 6.0f;
 | |
| 	DebugAccelF("(dix ptracc) profile average [%.2f ... %.2f] is %.3f\n",
 | |
| 	            vel->velocity, vel->last_velocity, res);
 | |
|         return res;
 | |
|     }else{
 | |
| 	res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc);
 | |
| 	DebugAccelF("(dix ptracc) profile sample [%.2f] is %.3f\n",
 | |
|                vel->velocity, res);
 | |
| 	return res;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*****************************************
 | |
|  *  Acceleration functions and profiles
 | |
|  ****************************************/
 | |
| 
 | |
| /**
 | |
|  * Polynomial function similar previous one, but with f(1) = 1
 | |
|  */
 | |
| static float
 | |
| PolynomialAccelerationProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float ignored,
 | |
|     float acc)
 | |
| {
 | |
|    return pow(velocity, (acc - 1.0) * 0.5);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * returns acceleration for velocity.
 | |
|  * This profile selects the two functions like the old scheme did
 | |
|  */
 | |
| static float
 | |
| ClassicProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc)
 | |
| {
 | |
|     if (threshold) {
 | |
| 	return SimpleSmoothProfile (pVel,
 | |
| 	                            velocity,
 | |
|                                     threshold,
 | |
|                                     acc);
 | |
|     } else {
 | |
| 	return PolynomialAccelerationProfile (pVel,
 | |
| 	                                      velocity,
 | |
|                                               0,
 | |
|                                               acc);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Power profile
 | |
|  * This has a completely smooth transition curve, i.e. no jumps in the
 | |
|  * derivatives.
 | |
|  *
 | |
|  * This has the expense of overall response dependency on min-acceleration.
 | |
|  * In effect, min_acceleration mimics const_acceleration in this profile.
 | |
|  */
 | |
| static float
 | |
| PowerProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc)
 | |
| {
 | |
|     float vel_dist;
 | |
| 
 | |
|     acc = (acc-1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */
 | |
| 
 | |
|     if (velocity <= threshold)
 | |
|         return pVel->min_acceleration;
 | |
|     vel_dist = velocity - threshold;
 | |
|     return (pow(acc, vel_dist)) * pVel->min_acceleration;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * just a smooth function in [0..1] -> [0..1]
 | |
|  *  - point symmetry at 0.5
 | |
|  *  - f'(0) = f'(1) = 0
 | |
|  *  - starts faster than a sinoid
 | |
|  *  - smoothness C1 (Cinf if you dare to ignore endpoints)
 | |
|  */
 | |
| static inline float
 | |
| CalcPenumbralGradient(float x){
 | |
|     x *= 2.0f;
 | |
|     x -= 1.0f;
 | |
|     return 0.5f + (x * sqrt(1.0f - x*x) + asin(x))/M_PI;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * acceleration function similar to classic accelerated/unaccelerated,
 | |
|  * but with smooth transition in between (and towards zero for adaptive dec.).
 | |
|  */
 | |
| static float
 | |
| SimpleSmoothProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc)
 | |
| {
 | |
|     if(velocity < 1.0f)
 | |
|         return CalcPenumbralGradient(0.5 + velocity*0.5) * 2.0f - 1.0f;
 | |
|     if(threshold < 1.0f)
 | |
|         threshold = 1.0f;
 | |
|     if (velocity <= threshold)
 | |
|         return 1;
 | |
|     velocity /= threshold;
 | |
|     if (velocity >= acc)
 | |
|         return acc;
 | |
|     else
 | |
|         return 1.0f + (CalcPenumbralGradient(velocity/acc) * (acc - 1.0f));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * This profile uses the first half of the penumbral gradient as a start
 | |
|  * and then scales linearly.
 | |
|  */
 | |
| static float
 | |
| SmoothLinearProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc)
 | |
| {
 | |
|     float res, nv;
 | |
| 
 | |
|     if(acc > 1.0f)
 | |
|         acc -= 1.0f; /*this is so acc = 1 is no acceleration */
 | |
|     else
 | |
|         return 1.0f;
 | |
| 
 | |
|     nv = (velocity - threshold) * acc * 0.5f;
 | |
| 
 | |
|     if(nv < 0){
 | |
|         res = 0;
 | |
|     }else if(nv < 2){
 | |
|         res = CalcPenumbralGradient(nv*0.25f)*2.0f;
 | |
|     }else{
 | |
|         nv -= 2.0f;
 | |
|         res = nv * 2.0f / M_PI  /* steepness of gradient at 0.5 */
 | |
|               + 1.0f; /* gradient crosses 2|1 */
 | |
|     }
 | |
|     res += pVel->min_acceleration;
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| static float
 | |
| LinearProfile(
 | |
|     DeviceVelocityPtr pVel,
 | |
|     float velocity,
 | |
|     float threshold,
 | |
|     float acc)
 | |
| {
 | |
|     return acc * velocity;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Set the profile by number.
 | |
|  * Intended to make profiles exchangeable at runtime.
 | |
|  * If you created a profile, give it a number here and in the header to
 | |
|  * make it selectable. In case some profile-specific init is needed, here
 | |
|  * would be a good place, since FreeVelocityData() also calls this with -1.
 | |
|  * returns FALSE (0) if profile number is unavailable.
 | |
|  */
 | |
| _X_EXPORT int
 | |
| SetAccelerationProfile(
 | |
|     DeviceVelocityPtr s,
 | |
|     int profile_num)
 | |
| {
 | |
|     PointerAccelerationProfileFunc profile;
 | |
|     switch(profile_num){
 | |
|         case -1:
 | |
|             profile = NULL;  /* Special case to uninit properly */
 | |
|             break;
 | |
|         case AccelProfileClassic:
 | |
|             profile = ClassicProfile;
 | |
|             break;
 | |
|         case AccelProfileDeviceSpecific:
 | |
|             if(NULL == s->deviceSpecificProfile)
 | |
|         	return FALSE;
 | |
|             profile = s->deviceSpecificProfile;
 | |
|             break;
 | |
|         case AccelProfilePolynomial:
 | |
|             profile = PolynomialAccelerationProfile;
 | |
|             break;
 | |
|         case AccelProfileSmoothLinear:
 | |
|             profile = SmoothLinearProfile;
 | |
|             break;
 | |
|         case AccelProfileSimple:
 | |
|             profile = SimpleSmoothProfile;
 | |
|             break;
 | |
|         case AccelProfilePower:
 | |
|             profile = PowerProfile;
 | |
|             break;
 | |
|         case AccelProfileLinear:
 | |
|             profile = LinearProfile;
 | |
|             break;
 | |
|         case AccelProfileReserved:
 | |
|             /* reserved for future use, e.g. a user-defined profile */
 | |
|         default:
 | |
|             return FALSE;
 | |
|     }
 | |
|     if(s->profile_private != NULL){
 | |
|         /* Here one could free old profile-private data */
 | |
|         xfree(s->profile_private);
 | |
|         s->profile_private = NULL;
 | |
|     }
 | |
|     /* Here one could init profile-private data */
 | |
|     s->Profile = profile;
 | |
|     s->statistics.profile_number = profile_num;
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| /**********************************************
 | |
|  * driver interaction
 | |
|  **********************************************/
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * device-specific profile
 | |
|  *
 | |
|  * The device-specific profile is intended as a hook for a driver
 | |
|  * which may want to provide an own acceleration profile.
 | |
|  * It should not rely on profile-private data, instead
 | |
|  * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends).
 | |
|  * Users may override or choose it.
 | |
|  */
 | |
| _X_EXPORT void
 | |
| SetDeviceSpecificAccelerationProfile(
 | |
|         DeviceVelocityPtr s,
 | |
|         PointerAccelerationProfileFunc profile)
 | |
| {
 | |
|     if(s)
 | |
| 	s->deviceSpecificProfile = profile;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if
 | |
|  * the predictable acceleration scheme is not in effect.
 | |
|  */
 | |
| _X_EXPORT DeviceVelocityPtr
 | |
| GetDevicePredictableAccelData(
 | |
| 	DeviceIntPtr pDev)
 | |
| {
 | |
|     /*sanity check*/
 | |
|     if(!pDev){
 | |
| 	ErrorF("[dix] accel: DeviceIntPtr was NULL");
 | |
| 	return NULL;
 | |
|     }
 | |
|     if( pDev->valuator &&
 | |
| 	pDev->valuator->accelScheme.AccelSchemeProc ==
 | |
| 	    acceleratePointerPredictable &&
 | |
| 	pDev->valuator->accelScheme.accelData != NULL){
 | |
| 
 | |
| 	return (DeviceVelocityPtr)pDev->valuator->accelScheme.accelData;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /********************************
 | |
|  *  acceleration schemes
 | |
|  *******************************/
 | |
| 
 | |
| /**
 | |
|  * Modifies valuators in-place.
 | |
|  * This version employs a velocity approximation algorithm to
 | |
|  * enable fine-grained predictable acceleration profiles.
 | |
|  */
 | |
| void
 | |
| acceleratePointerPredictable(
 | |
|     DeviceIntPtr pDev,
 | |
|     int first_valuator,
 | |
|     int num_valuators,
 | |
|     int *valuators,
 | |
|     int evtime)
 | |
| {
 | |
|     float mult = 0.0;
 | |
|     int dx = 0, dy = 0;
 | |
|     int *px = NULL, *py = NULL;
 | |
|     DeviceVelocityPtr velocitydata =
 | |
| 	(DeviceVelocityPtr) pDev->valuator->accelScheme.accelData;
 | |
|     float fdx, fdy; /* no need to init */
 | |
| 
 | |
|     if (!num_valuators || !valuators || !velocitydata)
 | |
|         return;
 | |
| 
 | |
|     if (first_valuator == 0) {
 | |
|         dx = valuators[0];
 | |
|         px = &valuators[0];
 | |
|     }
 | |
|     if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) {
 | |
|         dy = valuators[1 - first_valuator];
 | |
|         py = &valuators[1 - first_valuator];
 | |
|     }
 | |
| 
 | |
|     if (dx || dy){
 | |
|         /* reset nonvisible state? */
 | |
|         if (ProcessVelocityData(velocitydata, dx , dy, evtime)) {
 | |
|             /* set to center of pixel. makes sense as long as there are no
 | |
|              * means of passing on sub-pixel values.
 | |
|              */
 | |
|             pDev->last.remainder[0] = pDev->last.remainder[1] = 0.5f;
 | |
|             /* prevent softening (somewhat quirky solution,
 | |
|             as it depends on the algorithm) */
 | |
|             velocitydata->last_dx = dx;
 | |
|             velocitydata->last_dy = dy;
 | |
|         }
 | |
| 
 | |
|         if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) {
 | |
|             /* invoke acceleration profile to determine acceleration */
 | |
|             mult = ComputeAcceleration (velocitydata,
 | |
| 					pDev->ptrfeed->ctrl.threshold,
 | |
| 					(float)pDev->ptrfeed->ctrl.num /
 | |
| 					(float)pDev->ptrfeed->ctrl.den);
 | |
| 
 | |
|             if(mult != 1.0 || velocitydata->const_acceleration != 1.0) {
 | |
|                 ApplySofteningAndConstantDeceleration( velocitydata,
 | |
|                                                        dx, dy,
 | |
|                                                        &fdx, &fdy,
 | |
|                                                        mult > 1.0);
 | |
|                 if (dx) {
 | |
|                     pDev->last.remainder[0] = mult * fdx + pDev->last.remainder[0];
 | |
|                     *px = (int)pDev->last.remainder[0];
 | |
|                     pDev->last.remainder[0] = pDev->last.remainder[0] - (float)*px;
 | |
|                 }
 | |
|                 if (dy) {
 | |
|                     pDev->last.remainder[1] = mult * fdy + pDev->last.remainder[1];
 | |
|                     *py = (int)pDev->last.remainder[1];
 | |
|                     pDev->last.remainder[1] = pDev->last.remainder[1] - (float)*py;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     /* remember last motion delta (for softening/slow movement treatment) */
 | |
|     velocitydata->last_dx = dx;
 | |
|     velocitydata->last_dy = dy;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Originally a part of xf86PostMotionEvent; modifies valuators
 | |
|  * in-place. Retained mostly for embedded scenarios.
 | |
|  */
 | |
| void
 | |
| acceleratePointerLightweight(
 | |
|     DeviceIntPtr pDev,
 | |
|     int first_valuator,
 | |
|     int num_valuators,
 | |
|     int *valuators,
 | |
|     int ignored)
 | |
| {
 | |
|     float mult = 0.0;
 | |
|     int dx = 0, dy = 0;
 | |
|     int *px = NULL, *py = NULL;
 | |
| 
 | |
|     if (!num_valuators || !valuators)
 | |
|         return;
 | |
| 
 | |
|     if (first_valuator == 0) {
 | |
|         dx = valuators[0];
 | |
|         px = &valuators[0];
 | |
|     }
 | |
|     if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) {
 | |
|         dy = valuators[1 - first_valuator];
 | |
|         py = &valuators[1 - first_valuator];
 | |
|     }
 | |
| 
 | |
|     if (!dx && !dy)
 | |
|         return;
 | |
| 
 | |
|     if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) {
 | |
|         /* modeled from xf86Events.c */
 | |
|         if (pDev->ptrfeed->ctrl.threshold) {
 | |
|             if ((abs(dx) + abs(dy)) >= pDev->ptrfeed->ctrl.threshold) {
 | |
|                 pDev->last.remainder[0] = ((float)dx *
 | |
|                                              (float)(pDev->ptrfeed->ctrl.num)) /
 | |
|                                              (float)(pDev->ptrfeed->ctrl.den) +
 | |
|                                             pDev->last.remainder[0];
 | |
|                 if (px) {
 | |
|                     *px = (int)pDev->last.remainder[0];
 | |
|                     pDev->last.remainder[0] = pDev->last.remainder[0] -
 | |
|                                                 (float)(*px);
 | |
|                 }
 | |
| 
 | |
|                 pDev->last.remainder[1] = ((float)dy *
 | |
|                                              (float)(pDev->ptrfeed->ctrl.num)) /
 | |
|                                              (float)(pDev->ptrfeed->ctrl.den) +
 | |
|                                             pDev->last.remainder[1];
 | |
|                 if (py) {
 | |
|                     *py = (int)pDev->last.remainder[1];
 | |
|                     pDev->last.remainder[1] = pDev->last.remainder[1] -
 | |
|                                                 (float)(*py);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
| 	    mult = pow((float)dx * (float)dx + (float)dy * (float)dy,
 | |
|                        ((float)(pDev->ptrfeed->ctrl.num) /
 | |
|                         (float)(pDev->ptrfeed->ctrl.den) - 1.0) /
 | |
|                        2.0) / 2.0;
 | |
|             if (dx) {
 | |
|                 pDev->last.remainder[0] = mult * (float)dx +
 | |
|                                             pDev->last.remainder[0];
 | |
|                 *px = (int)pDev->last.remainder[0];
 | |
|                 pDev->last.remainder[0] = pDev->last.remainder[0] -
 | |
|                                             (float)(*px);
 | |
|             }
 | |
|             if (dy) {
 | |
|                 pDev->last.remainder[1] = mult * (float)dy +
 | |
|                                             pDev->last.remainder[1];
 | |
|                 *py = (int)pDev->last.remainder[1];
 | |
|                 pDev->last.remainder[1] = pDev->last.remainder[1] -
 | |
|                                             (float)(*py);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |