Страницы

Поиск по вопросам

понедельник, 25 февраля 2019 г.

Как определить прямые и ломаные движения мыши?

Как на 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}

Прямая!

Проведите мышкой внутри этого прямоугольника:

 

Комментариев нет:

Отправить комментарий