/* find the most plausible velocity. That is, the most distant * (in time) tracker which isn't too old, beyond a linear partition, * or simply too much off initial velocity. * * May return 0. */ static float QueryTrackers(DeviceVelocityPtr vel, int cur_t){ int n, offset, dir = 255, i = -1, age_ms; /* initial velocity: a low-offset, valid velocity */ float iveloc = 0, res = 0, tmp, vdiff; float vfac = vel->corr_mul * vel->const_acceleration; /* premultiply */ /* loop from current to older data */ for(offset = 1; offset < vel->num_tracker; offset++){ n = TRACKER_INDEX(vel, offset); age_ms = cur_t - vel->tracker[n].time; /* bail out if data is too old and protect from overrun */ if (age_ms >= vel->reset_time || age_ms < 0) { DebugAccelF("(dix prtacc) query: tracker too old\n"); break; } /* * this heuristic avoids using the linear-motion velocity formula * in CalcTracker() on motion that isn't exactly linear. So to get * even more precision we could subdivide as a final step, so possible * non-linearities are accounted for. */ dir &= vel->tracker[n].dir; if(dir == 0){ DebugAccelF("(dix prtacc) query: no longer linear\n"); /* instead of breaking it we might also inspect the partition after, * but actual improvement with this is probably rare. */ break; } tmp = CalcTracker(vel, offset, cur_t) * vfac; if ((iveloc == 0 || offset <= vel->initial_range) && tmp != 0) { /* set initial velocity and result */ res = iveloc = tmp; i = offset; } else if (iveloc != 0 && tmp != 0) { vdiff = fabs(iveloc - tmp); if (vdiff <= vel->max_diff || vdiff/(iveloc + tmp) < vel->max_rel_diff) { /* we're in range with the initial velocity, * so this result is likely better * (it contains more information). */ res = tmp; i = offset; }else{ /* we're not in range, quit - it won't get better. */ DebugAccelF("(dix prtacc) query: tracker too different:" " old %2.2f initial %2.2f diff: %2.2f\n", tmp, iveloc, vdiff); break; } } } if(offset == vel->num_tracker){ DebugAccelF("(dix prtacc) query: last tracker in effect\n"); i = vel->num_tracker-1; } if(i>=0){ n = TRACKER_INDEX(vel, i); DebugAccelF("(dix prtacc) result: offset %i [dx: %i dy: %i diff: %i]\n", i, vel->tracker[n].dx, vel->tracker[n].dy, cur_t - vel->tracker[n].time); } return res; }
/** * Modifies valuators in-place. * This version employs a velocity approximation algorithm to * enable fine-grained predictable acceleration profiles. */ void acceleratePointerPredictable( DeviceIntPtr dev, ValuatorMask* val, CARD32 evtime) { float fdx, fdy, tmp, mult; /* no need to init */ int dx = 0, dy = 0, tmpi; DeviceVelocityPtr velocitydata = GetDevicePredictableAccelData(dev); Bool soften = TRUE; if (!velocitydata) return; if (velocitydata->statistics.profile_number == AccelProfileNone && velocitydata->const_acceleration == 1.0f) { return; /*we're inactive anyway, so skip the whole thing.*/ } if (valuator_mask_isset(val, 0)) { dx = valuator_mask_get(val, 0); } if (valuator_mask_isset(val, 1)) { dy = valuator_mask_get(val, 1); } if (dx || dy){ /* reset non-visible state? */ if (ProcessVelocityData2D(velocitydata, dx , dy, evtime)) { soften = FALSE; } if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { /* invoke acceleration profile to determine acceleration */ mult = ComputeAcceleration (dev, velocitydata, dev->ptrfeed->ctrl.threshold, (float)dev->ptrfeed->ctrl.num / (float)dev->ptrfeed->ctrl.den); if(mult != 1.0f || velocitydata->const_acceleration != 1.0f) { ApplySofteningAndConstantDeceleration(velocitydata, dx, dy, &fdx, &fdy, (mult > 1.0f) && soften); if (dx) { tmp = mult * fdx + dev->last.remainder[0]; /* Since it may not be apparent: lrintf() does not offer * strong statements about rounding; however because we * process each axis conditionally, there's no danger * of a toggling remainder. Its lack of guarantees likely * makes it faster on the average target. */ tmpi = lrintf(tmp); valuator_mask_set(val, 0, tmpi); dev->last.remainder[0] = tmp - (float)tmpi; } if (dy) { tmp = mult * fdy + dev->last.remainder[1]; tmpi = lrintf(tmp); valuator_mask_set(val, 1, tmpi); dev->last.remainder[1] = tmp - (float)tmpi; } DebugAccelF("pos (%i | %i) remainders x: %.3f y: %.3f delta x:%.3f y:%.3f\n", *px, *py, dev->last.remainder[0], dev->last.remainder[1], fdx, fdy); } } } /* remember last motion delta (for softening/slow movement treatment) */ velocitydata->last_dx = dx; velocitydata->last_dy = dy; }
/** * Modifies valuators in-place. * This version employs a velocity approximation algorithm to * enable fine-grained predictable acceleration profiles. */ void acceleratePointerPredictable( DeviceIntPtr dev, 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) dev->valuator->accelScheme.accelData; float fdx, fdy, tmp; /* no need to init */ Bool soften = TRUE; if (!num_valuators || !valuators || !velocitydata) return; if (velocitydata->statistics.profile_number == AccelProfileNone && velocitydata->const_acceleration == 1.0f) { return; /*we're inactive anyway, so skip the whole thing.*/ } 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 non-visible state? */ if (ProcessVelocityData2D(velocitydata, dx , dy, evtime)) { soften = FALSE; } if (dev->ptrfeed && dev->ptrfeed->ctrl.num) { /* invoke acceleration profile to determine acceleration */ mult = ComputeAcceleration (dev, velocitydata, dev->ptrfeed->ctrl.threshold, (float)dev->ptrfeed->ctrl.num / (float)dev->ptrfeed->ctrl.den); if(mult != 1.0 || velocitydata->const_acceleration != 1.0) { ApplySofteningAndConstantDeceleration( velocitydata, dx, dy, &fdx, &fdy, (mult > 1.0) && soften); if (dx) { tmp = mult * fdx + dev->last.remainder[0]; /* Since it may not be apparent: lrintf() does not offer * strong statements about rounding; however because we * process each axis conditionally, there's no danger * of a toggling remainder. Its lack of guarantees likely * makes it faster on the average target. */ *px = lrintf(tmp); dev->last.remainder[0] = tmp - (float)*px; } if (dy) { tmp = mult * fdy + dev->last.remainder[1]; *py = lrintf(tmp); dev->last.remainder[1] = tmp - (float)*py; } DebugAccelF("pos (%i | %i) remainders x: %.3f y: %.3f delta x:%.3f y:%.3f\n", *px, *py, dev->last.remainder[0], dev->last.remainder[1], fdx, fdy); } } } /* remember last motion delta (for softening/slow movement treatment) */ velocitydata->last_dx = dx; velocitydata->last_dy = dy; }
/* find the most plausible velocity. That is, the most distant * (in time) tracker which isn't too old, the movement vector was * in the same octant, and where the velocity is within an * acceptable range to the inital velocity. * * @return The tracker's velocity or 0 if the above conditions are unmet */ static double QueryTrackers(DeviceVelocityPtr vel, int cur_t) { int offset, dir = UNDEFINED, used_offset = -1, age_ms; /* initial velocity: a low-offset, valid velocity */ double initial_velocity = 0, result = 0, velocity_diff; double velocity_factor = vel->corr_mul * vel->const_acceleration; /* premultiply */ /* loop from current to older data */ for (offset = 1; offset < vel->num_tracker; offset++) { MotionTracker *tracker = TRACKER(vel, offset); double tracker_velocity; age_ms = cur_t - tracker->time; /* bail out if data is too old and protect from overrun */ if (age_ms >= vel->reset_time || age_ms < 0) { DebugAccelF("query: tracker too old (reset after %d, age is %d)\n", vel->reset_time, age_ms); break; } /* * this heuristic avoids using the linear-motion velocity formula * in CalcTracker() on motion that isn't exactly linear. So to get * even more precision we could subdivide as a final step, so possible * non-linearities are accounted for. */ dir &= tracker->dir; if (dir == 0) { /* we've changed octant of movement (e.g. NE → NW) */ DebugAccelF("query: no longer linear\n"); /* instead of breaking it we might also inspect the partition after, * but actual improvement with this is probably rare. */ break; } tracker_velocity = CalcTracker(tracker, cur_t) * velocity_factor; if ((initial_velocity == 0 || offset <= vel->initial_range) && tracker_velocity != 0) { /* set initial velocity and result */ result = initial_velocity = tracker_velocity; used_offset = offset; } else if (initial_velocity != 0 && tracker_velocity != 0) { velocity_diff = fabs(initial_velocity - tracker_velocity); if (velocity_diff > vel->max_diff && velocity_diff / (initial_velocity + tracker_velocity) >= vel->max_rel_diff) { /* we're not in range, quit - it won't get better. */ DebugAccelF("query: tracker too different:" " old %2.2f initial %2.2f diff: %2.2f\n", tracker_velocity, initial_velocity, velocity_diff); break; } /* we're in range with the initial velocity, * so this result is likely better * (it contains more information). */ result = tracker_velocity; used_offset = offset; } } if (offset == vel->num_tracker) { DebugAccelF("query: last tracker in effect\n"); used_offset = vel->num_tracker - 1; } if (used_offset >= 0) { #ifdef PTRACCEL_DEBUGGING MotionTracker *tracker = TRACKER(vel, used_offset); DebugAccelF("result: offset %i [dx: %f dy: %f diff: %i]\n", used_offset, tracker->dx, tracker->dy, cur_t - tracker->time); #endif } return result; }
/** * 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; }