Commit b437b2f1 authored by Daniel van Vugt's avatar Daniel van Vugt Committed by Peter Hutterer

Introduce omnidirectional (elliptical) hysteresis

This changes the hysteresis region to an ellipse (usually a circle), where
previously it was a rectangle (usually square).

Using an ellipse means the algorithm is no longer more sensitive in some
directions than others. It is now omnidirectional, which solves a few
problems:
  * Moving a finger in small circles now creates circles, not squares.
  * Moving a finger in a curve no longer snaps the cursor to vertical
    or horizontal lines. The cursor now follows a similar curve to the
    finger.

https://bugs.freedesktop.org/page.cgi?id=splinter.html&bug=105306Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 6936a155)
parent 1c15e162
......@@ -127,12 +127,9 @@ fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch,
if (!dispatch->mt.want_hysteresis)
return false;
point.x = evdev_hysteresis(slot->point.x,
slot->hysteresis_center.x,
dispatch->mt.hysteresis_margin.x);
point.y = evdev_hysteresis(slot->point.y,
slot->hysteresis_center.y,
dispatch->mt.hysteresis_margin.y);
point = evdev_hysteresis(&slot->point,
&slot->hysteresis_center,
&dispatch->mt.hysteresis_margin);
slot->hysteresis_center = slot->point;
if (point.x == slot->point.x && point.y == slot->point.y)
......
......@@ -194,26 +194,15 @@ static inline void
tp_motion_hysteresis(struct tp_dispatch *tp,
struct tp_touch *t)
{
int x = t->point.x,
y = t->point.y;
if (!tp->hysteresis.enabled)
return;
if (t->history.count == 0) {
t->hysteresis.center = t->point;
} else {
x = evdev_hysteresis(x,
t->hysteresis.center.x,
tp->hysteresis.margin.x);
y = evdev_hysteresis(y,
t->hysteresis.center.y,
tp->hysteresis.margin.y);
t->hysteresis.center.x = x;
t->hysteresis.center.y = y;
t->point.x = x;
t->point.y = y;
}
if (t->history.count > 0)
t->point = evdev_hysteresis(&t->point,
&t->hysteresis.center,
&tp->hysteresis.margin);
t->hysteresis.center = t->point;
}
static inline void
......
......@@ -600,11 +600,11 @@ evdev_to_left_handed(struct evdev_device *device,
* Apply a hysteresis filtering to the coordinate in, based on the current
* hysteresis center and the margin. If 'in' is within 'margin' of center,
* return the center (and thus filter the motion). If 'in' is outside,
* return a point on the edge of the new margin. So for a point x in the
* space outside c + margin we return r:
* +---+ +---+
* return a point on the edge of the new margin (which is an ellipse, usually
* a circle). So for a point x in the space outside c + margin we return r:
* ,---. ,---.
* | c | x → | r x
* +---+ +---+
* `---' `---'
*
* The effect of this is that initial small motions are filtered. Once we
* move into one direction we lag the real coordinates by 'margin' but any
......@@ -617,41 +617,71 @@ evdev_to_left_handed(struct evdev_device *device,
* Otherwise, the center has a dead zone of size margin around it and the
* first reachable point is the margin edge.
*
* Hysteresis is handled separately per axis (and the window is thus
* rectangular, not circular). It is unkown if that's an issue, but the
* calculation to do circular hysteresis are nontrivial, especially since
* many touchpads have uneven x/y resolutions.
*
* Given coordinates, 0, 1, 2, ... this is what we return for a margin of 3
* and a center of 0:
*
* Input: 1 2 3 4 5 6 5 4 3 2 1 0 -1
* Coord: 0 0 0 1 2 3 3 3 3 3 3 3 2
* Center: 0 0 0 1 2 3 3 3 3 3 3 3 2
*
* Problem: viewed from a stationary finger that starts moving, the
* hysteresis margin is M in both directions. Once we start moving
* continuously though, the margin is 0 in the movement direction and 2*M to
* change direction. That makes the finger less responsive to directional
* changes than to the original movement.
*
* @param in The input coordinate
* @param center Current center of the hysteresis
* @param margin Hysteresis width (on each side)
*
* @return The new center of the hysteresis
*/
static inline int
evdev_hysteresis(int in, int center, int margin)
static inline struct device_coords
evdev_hysteresis(const struct device_coords *in,
const struct device_coords *center,
const struct device_coords *margin)
{
int diff = in - center;
if (abs(diff) <= margin)
return center;
if (diff > 0)
return in - margin;
else
return in + margin;
int dx = in->x - center->x;
int dy = in->y - center->y;
int dx2 = dx * dx;
int dy2 = dy * dy;
int a = margin->x;
int b = margin->y;
double normalized_finger_distance, finger_distance, margin_distance;
double lag_x, lag_y;
struct device_coords result;
if (!a || !b)
return *in;
/*
* Basic equation for an ellipse of radii a,b:
* x²/a² + y²/b² = 1
* But we start by making a scaled ellipse passing through the
* relative finger location (dx,dy). So the scale of this ellipse is
* the ratio of finger_distance to margin_distance:
* dx²/a² + dy²/b² = normalized_finger_distance²
*/
normalized_finger_distance = sqrt((double)dx2 / (a * a) +
(double)dy2 / (b * b));
/* Which means anything less than 1 is within the elliptical margin */
if (normalized_finger_distance < 1.0)
return *center;
finger_distance = sqrt(dx2 + dy2);
margin_distance = finger_distance / normalized_finger_distance;
/*
* Now calculate the x,y coordinates on the edge of the margin ellipse
* where it intersects the finger vector. Shortcut: We achieve this by
* finding the point with the same gradient as dy/dx.
*/
if (dx) {
double gradient = (double)dy / dx;
lag_x = margin_distance / sqrt(gradient * gradient + 1);
lag_y = sqrt((margin_distance + lag_x) *
(margin_distance - lag_x));
} else { /* Infinite gradient */
lag_x = 0.0;
lag_y = margin_distance;
}
/*
* 'result' is the centre of an ellipse (radii a,b) which has been
* dragged by the finger moving inside it to 'in'. The finger is now
* touching the margin ellipse at some point: (±lag_x,±lag_y)
*/
result.x = (dx >= 0) ? in->x - lag_x : in->x + lag_x;
result.y = (dy >= 0) ? in->y - lag_y : in->y + lag_y;
return result;
}
static inline struct libinput *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment