weston/src/filter.c

337 lines
7.8 KiB
C
Raw Normal View History

/*
* 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 <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;
}