Как на Javascript отловить - абсолютно прямые и ломаные движения мыши, исключая кривые.
Ответ
Алгоритм и псевдокод
Здесь у нас будут присутствовать такие сущности: класс Point, контейнер Points, и переменные его типа: Input — хранит все записанные координаты мыши, Output — будет хранить точки ломаной (или отрезка, если проведена прямая линия).
К тому же, нам понадобятся функции для нахождения угла между двумя точками Angle.Find (Point, Point) и функция нахождения модуля разницы двух углов Angle.Difference (double, double)
Для решения задачи будем проходиться по всем точкам и находить примерно ровные отрезки. И да, максимальное отклонение для линии будет в константе Angle.Eps
// начало ломаной
Output.Add(Input[0]);
// координаты текущего отрезка
Point begin Input[0];
Point end = Input[1];
// угол для сверки
double angle = Angle.Find(begin, end);
// обработка всех точек
for (int i = 2; i < Input.Length - 1; i++) {
double currentAngle = Angle.Find(begin, Input[i]);
if (Angle.Difference(currentAngle, angle) < Angle.Max) {
end = Input[i];
//TODO: можно также высчитывать угол по всем точкам отрезка
} else {
// сохраняем старый отрезок
Output.Add(end);
// создаём новый отрезок
begin = Input[i];
end = Input [++i];
angle = Angle.Find(begin, end);
}
}
Output.Add(end);
В результате, если все точки на одной прямой, Output будет содержать только две точки: начало и конец отрезка. Если их больше — линия ломаная. А если у ломаной линии расстояние между точками очень мало — это кривая линия. Проверка на кривую линию может быть достаточно простая:
bool crooked = false;
for (int i = 1; i < Output.Length; i++) {
// функция вычисления расстояния между точками и эпсилон для длины отрезков
if (Point.Distance(Output[i], Output[i - 1]) < Point.Eps) {
crooked = true;
break;
}
}
На всякий случай небольшое уточнение. Я выделил Angle.Difference потому, что просто так вычитать углы нельзя — для углов в 1 и 359 градусов разница всего лишь два, а не 358. Её можно определить так:
double Angle::Difference (double a, double b) {
if (a > 270 && b < 90) {// 3*Pi/2, Pi/2, если используете радианы
b += 360; // 2 *Pi
} else if (b > 270 && a < 90) {
a += 360;
}
return abs(a - b);
}
Рабочая JS-версия
// -- Вспомогательные функции --
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function angleDif(a1, a2) {
if (a1 > 270 && a2 < 90) {
a2 += 360;
} else if (a2 > 270 && a1 < 90) {
a1 += 360;
}
return Math.abs(a1 - a2);
}
function angleFind(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
}
function distance(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
// -- Инициализация --
var canvas = document.getElementById('myCanvas');
var answer = document.getElementById('answer');
var ctx = canvas.getContext('2d');
var input = [];
var output = [];
var previous = {x:-64, y:-64};
ctx.fillStyle="#CDBCFF";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// -- Константы --
// Отклонение в градусах, при котором прямая считается ломаной,
// и появляется изгиб.
SMALL_ANGLE = 9;
// Расстояние в пикселях между изгибами,
// меньше которого они считаются резкими.
BEND_DIST = 10;
// Число резких изгибов, при котором ломаная считается кривой.
NEED_BENDS = 5;
// -- Основные функции --
// Обрабатывает событие движения мыши.
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
var current = {x:mousePos.x, y:mousePos.y}
// не сохраняем мусорные точки
if (distance(current, previous) > 2) {
previous = {x: mousePos.x, y: mousePos.y};
input.push(previous);
process();
output.push({x: mousePos.x, y: mousePos.y});
drawResult();
}
ctx.fillStyle = "#000000";
}, false);
// Отрисовывает линию.
function drawResult() {
if (output.length >= 2) {
ctx.fillStyle="#CDBCFF";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.moveTo(output[1].x, output[1].y);
for (var i = 2; i < output.length; i ++) {
ctx.lineTo(output[i].x, output[i].y);
}
ctx.stroke();
}
}
// Выбирает точки для отрисовки и определяет тип линии.
function process() {
if (input.length < 2)
return;
// по этим двум точкам определяется текущее направление линии
var begin = input[0];
var last = null;
// текущая точка
var current;
// текущее направление линии
var angle = 0;
// направление к текущей точке
var curAngle = 0;
// количество резких изгибов
var bends = 0;
// список точек для отрисовки
output = [];
output.push(begin);
for (var i = 1; i < input.length; i++) {
current = input[i];
if (!last) {
if (distance(begin, current) > 2) {
last = current;
angle = angleFind(begin, last);
}
continue;
}
curAngle = angleFind(begin, current);
// находим угол последнего изгиба
if (angleDif(angle, curAngle) < SMALL_ANGLE) {
// если угол мало меняется, то перейти
// к следующей точке
last = current;
} else {
// если угол большой при малом расстоянии,
// то вероятно, что у нас кривая
if (distance(begin, current) < BEND_DIST) {
bends += 1;
}
// если угол большой, то у нас не прямая
// нужно добавить новую точку
output.push(last);
begin = current;
last = null;
}
}
// убрать "Прямая" если много точек
if (output.length > 1) {
// можно менять число перегибов нужное для кривой
if (bends >= NEED_BENDS) {
answer.innerHTML = "Кривая!";
} else {
answer.innerHTML = "Ломаная!";
}
}
}
body {background-color:#FFF}
#myCanvas {border:1px solid #999}
Проведите мышкой внутри этого прямоугольника:
Комментариев нет:
Отправить комментарий