summaryrefslogtreecommitdiff
path: root/src/filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filter.c')
-rw-r--r--src/filter.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/src/filter.c b/src/filter.c
new file mode 100644
index 00000000..a55ebf27
--- /dev/null
+++ b/src/filter.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright © 2012 Jonas Ådahl
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <math.h>
+
+#include <wayland-util.h>
+
+#include "compositor.h"
+#include "filter.h"
+
+void
+weston_filter_dispatch(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time)
+{
+ filter->interface->filter(filter, motion, data, time);
+}
+
+/*
+ * Pointer acceleration filter
+ */
+
+#define MAX_VELOCITY_DIFF 1.0
+#define MOTION_TIMEOUT 300 /* (ms) */
+#define NUM_POINTER_TRACKERS 16
+
+struct pointer_tracker {
+ double dx;
+ double dy;
+ uint32_t time;
+ int dir;
+};
+
+struct pointer_accelerator;
+struct pointer_accelerator {
+ struct weston_motion_filter base;
+
+ accel_profile_func_t profile;
+
+ double velocity;
+ double last_velocity;
+ int last_dx;
+ int last_dy;
+
+ struct pointer_tracker *trackers;
+ int cur_tracker;
+};
+
+enum directions {
+ N = 1 << 0,
+ NE = 1 << 1,
+ E = 1 << 2,
+ SE = 1 << 3,
+ S = 1 << 4,
+ SW = 1 << 5,
+ W = 1 << 6,
+ NW = 1 << 7,
+ UNDEFINED_DIRECTION = 0xff
+};
+
+static int
+get_direction(int dx, int dy)
+{
+ int dir = UNDEFINED_DIRECTION;
+ int d1, d2;
+ double r;
+
+ if (abs(dx) < 2 && abs(dy) < 2) {
+ if (dx > 0 && dy > 0)
+ dir = S | SE | E;
+ else if (dx > 0 && dy < 0)
+ dir = N | NE | E;
+ else if (dx < 0 && dy > 0)
+ dir = S | SW | W;
+ else if (dx < 0 && dy < 0)
+ dir = N | NW | W;
+ else if (dx > 0)
+ dir = NW | W | SW;
+ else if (dx < 0)
+ dir = NE | E | SE;
+ else if (dy > 0)
+ dir = SE | S | SW;
+ else if (dy < 0)
+ dir = NE | N | NW;
+ }
+ else {
+ /* Calculate r within the interval [0 to 8)
+ *
+ * r = [0 .. 2π] where 0 is North
+ * d_f = r / 2π ([0 .. 1))
+ * d_8 = 8 * d_f
+ */
+ r = atan2(dy, dx);
+ r = fmod(r + 2.5*M_PI, 2*M_PI);
+ r *= 4*M_1_PI;
+
+ /* Mark one or two close enough octants */
+ d1 = (int)(r + 0.9) % 8;
+ d2 = (int)(r + 0.1) % 8;
+
+ dir = (1 << d1) | (1 << d2);
+ }
+
+ return dir;
+}
+
+static void
+feed_trackers(struct pointer_accelerator *accel,
+ double dx, double dy,
+ uint32_t time)
+{
+ int i, current;
+ struct pointer_tracker *trackers = accel->trackers;
+
+ for (i = 0; i < NUM_POINTER_TRACKERS; i++) {
+ trackers[i].dx += dx;
+ trackers[i].dy += dy;
+ }
+
+ current = (accel->cur_tracker + 1) % NUM_POINTER_TRACKERS;
+ accel->cur_tracker = current;
+
+ trackers[current].dx = 0.0;
+ trackers[current].dy = 0.0;
+ trackers[current].time = time;
+ trackers[current].dir = get_direction(dx, dy);
+}
+
+static struct pointer_tracker *
+tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset)
+{
+ unsigned int index =
+ (accel->cur_tracker + NUM_POINTER_TRACKERS - offset)
+ % NUM_POINTER_TRACKERS;
+ return &accel->trackers[index];
+}
+
+static double
+calculate_tracker_velocity(struct pointer_tracker *tracker, uint32_t time)
+{
+ int dx;
+ int dy;
+ double distance;
+
+ dx = tracker->dx;
+ dy = tracker->dy;
+ distance = sqrt(dx*dx + dy*dy);
+ return distance / (double)(time - tracker->time);
+}
+
+static double
+calculate_velocity(struct pointer_accelerator *accel, uint32_t time)
+{
+ struct pointer_tracker *tracker;
+ double velocity;
+ double result = 0.0;
+ double initial_velocity;
+ double velocity_diff;
+ unsigned int offset;
+
+ unsigned int dir = tracker_by_offset(accel, 0)->dir;
+
+ /* Find first velocity */
+ for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) {
+ tracker = tracker_by_offset(accel, offset);
+
+ if (time <= tracker->time)
+ continue;
+
+ result = initial_velocity =
+ calculate_tracker_velocity(tracker, time);
+ if (initial_velocity > 0.0)
+ break;
+ }
+
+ /* Find least recent vector within a timelimit, maximum velocity diff
+ * and direction threshold. */
+ for (; offset < NUM_POINTER_TRACKERS; offset++) {
+ tracker = tracker_by_offset(accel, offset);
+
+ /* Stop if too far away in time */
+ if (time - tracker->time > MOTION_TIMEOUT ||
+ tracker->time > time)
+ break;
+
+ /* Stop if direction changed */
+ dir &= tracker->dir;
+ if (dir == 0)
+ break;
+
+ velocity = calculate_tracker_velocity(tracker, time);
+
+ /* Stop if velocity differs too much from initial */
+ velocity_diff = fabs(initial_velocity - velocity);
+ if (velocity_diff > MAX_VELOCITY_DIFF)
+ break;
+
+ result = velocity;
+ }
+
+ return result;
+}
+
+static double
+acceleration_profile(struct pointer_accelerator *accel,
+ void *data, double velocity, uint32_t time)
+{
+ return accel->profile(&accel->base, data, velocity, time);
+}
+
+static double
+calculate_acceleration(struct pointer_accelerator *accel,
+ void *data, double velocity, uint32_t time)
+{
+ double factor;
+
+ /* Use Simpson's rule to calculate the avarage acceleration between
+ * the previous motion and the most recent. */
+ factor = acceleration_profile(accel, data, velocity, time);
+ factor += acceleration_profile(accel, data, accel->last_velocity, time);
+ factor += 4.0 *
+ acceleration_profile(accel, data,
+ (accel->last_velocity + velocity) / 2,
+ time);
+
+ factor = factor / 6.0;
+
+ return factor;
+}
+
+static double
+soften_delta(double last_delta, double delta)
+{
+ if (delta < -1.0 || delta > 1.0) {
+ if (delta > last_delta)
+ return delta - 0.5;
+ else if (delta < last_delta)
+ return delta + 0.5;
+ }
+
+ return delta;
+}
+
+static void
+apply_softening(struct pointer_accelerator *accel,
+ struct weston_motion_params *motion)
+{
+ motion->dx = soften_delta(accel->last_dx, motion->dx);
+ motion->dy = soften_delta(accel->last_dy, motion->dy);
+}
+
+static void
+accelerator_filter(struct weston_motion_filter *filter,
+ struct weston_motion_params *motion,
+ void *data, uint32_t time)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+ double velocity;
+ double accel_value;
+
+ feed_trackers(accel, motion->dx, motion->dy, time);
+ velocity = calculate_velocity(accel, time);
+ accel_value = calculate_acceleration(accel, data, velocity, time);
+
+ motion->dx = accel_value * motion->dx;
+ motion->dy = accel_value * motion->dy;
+
+ apply_softening(accel, motion);
+
+ accel->last_dx = motion->dx;
+ accel->last_dy = motion->dy;
+
+ accel->last_velocity = velocity;
+}
+
+static void
+accelerator_destroy(struct weston_motion_filter *filter)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+
+ free(accel->trackers);
+ free(accel);
+}
+
+struct weston_motion_filter_interface accelerator_interface = {
+ accelerator_filter,
+ accelerator_destroy
+};
+
+struct weston_motion_filter *
+create_pointer_accelator_filter(accel_profile_func_t profile)
+{
+ struct pointer_accelerator *filter;
+
+ filter = malloc(sizeof *filter);
+ if (filter == NULL)
+ return NULL;
+
+ filter->base.interface = &accelerator_interface;
+ wl_list_init(&filter->base.link);
+
+ filter->profile = profile;
+ filter->last_velocity = 0.0;
+ filter->last_dx = 0;
+ filter->last_dy = 0;
+
+ filter->trackers =
+ calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
+ filter->cur_tracker = 0;
+
+ return &filter->base;
+}