Question:
How to catch in Javascript – absolutely straight and broken mouse movements, excluding curves.
Answer:
Algorithm and pseudocode
Here we will have the following entities: the Point
class, the Points
container, and variables of its type: Input
– stores all recorded mouse coordinates, Output
– will store the points of a polyline (or a segment, if a straight line is drawn).
In addition, we will need functions for finding the angle between two points Angle.Find (Point, Point)
and the function for finding the modulus of the difference between two angles Angle.Difference (double, double)
.
To solve the problem, we will go through all the points and find approximately even segments. And yes, the maximum deviation for the line will be in the Angle.Eps
constant.
// начало ломаной
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);
As a result, if all points are on one straight line, Output
will contain only two points: the beginning and the end of the segment. If there are more of them, the line is broken. And if a broken line has a very small distance between points, it is a curved line. Checking for a curved line can be quite simple:
bool crooked = false;
for (int i = 1; i < Output.Length; i++) {
// функция вычисления расстояния между точками и эпсилон для длины отрезков
if (Point.Distance(Output[i], Output[i - 1]) < Point.Eps) {
crooked = true;
break;
}
}
Just in case, a little clarification. I selected Angle.Difference
because you can't just subtract angles – for angles of 1 and 359 degrees the difference is only two, not 358. It can be defined as follows:
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);
}
Working JS version
// -- Вспомогательные функции --
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}
<div id="answer">Прямая!</div>
<p>Проведите мышкой внутри этого прямоугольника:</p>
<canvas id="myCanvas" width="578" height="200"></canvas>