.isPointInPath() for stroked lines and polylines - javascript

I need a mechanism for detecting mouseover event for lines, curves and polylines which have a various stroke width, I have already made such a mechanism for rectangles and ellipses, so I'm not new with canvas API. I do outline all of the drawn objects and detect mouse position over them, when rectangles or ellipses have a stroke width more than 1 pixel I expand the path so that it contains the border too. For lines and polylines it is difficult for me to understand how should I expand them when I have a lineWidth of 20 pixels for example.
My question is: how to transform lines, curves and polylines in some shape path, so that this path could contain all their width?
I would need that the path created would contain the line / curve width represented with black in this image.
----------Some more information----------
I will try to simplify the problem:
We have 2 points (represented in red on the image below), they form a line that have a specific formula (y = mx + n), I need to dermine the formulas of perpendicular lines that are passing through these two initial points, after, it is necessary to determine the positions of the "blue" points, which are at the distance of the half of the value of context.lineWidth, when all points have been determined it is possible to create a new path using moveTo() and lineTo() sequence. This method should be applicable for quadratic and bezier curves using control points. The problem only remains in these mathematical calculations.

Have you tried using the .isPointInStroke() method of the Canvas 2D API:
ctx.isPointInStroke(x, y);
ctx.isPointInStroke(path, x, y);
Take a look at:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke

I found the solution on math.stackexchange here, this solution is for lines only, but it is applicable for curves and polylines with some specific modifications. First of all, we need to determine the formula of the line which passes through the two initials points:
Step 1
Our points: P1(x1, y1) and P2(x2, y2)
Distance between points and their neighbors: d
General form: Ax + By + C = 0
Where: A = y2 - y1; B = x1 - x2; C = x2y1 - x1y2.
After, it is necessary to define this formula in the short form:
Step 2
Short form: y = mx +n
Where: m = - A / B; n = - C / B. (when B != 0)
If A == 0 then we have the formula: y = C (case when we have a horizontal line)
If B == 0 then we have the formula: x = C (case when we have a vertical line)
When we have the slope of the line, we need the slope of the perpendicular line on it:
Step 3
Perpendiculat line slope: m2 = - 1 / m
If A == 0 or B == 0 go to Step 4
Now we need to get the neighbor points for both initial points:
Step 4
I will note neighbor points as P1N1, P1N2 for the first point and P2N1 and P2N2 for the second one
For the special cases (horizontal and vertical lines, when A == 0 or B == 0) we will have:
For A == 0 (horizontal line):
P1N1(x1, y1 - d / 2); P1N2(x1, y1 + d / 2); P2N1(x2, y2 + d / 2); P2N2(x2, y2 - d / 2).
For B == 0 (vertical line):
P1N1(x1 - d / 2, y1); P1N2(x1 + d / 2, y1); P2N1(x2 + d / 2, y2); P2N2(x2 - d / 2, y2).
For other cases (A != 0 and B != 0):
P1N1:
x = (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x1;
y = (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y1;
P1N2:
x = - (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x1;
y = - (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y1;
P2N1:
x = (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x2;
y = (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y2;
P2N2:
x = - (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x2;
y = - (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y2;
If you want to implement these formulas in your application you should cache some results to improve performance.

If you bezier curve is of the following form.
x(t) = x0 + x1*t + x2*t*t + x3*t*t*t
y(t) = y0 + y1*t + y2*t*t + y3*t*t*t
Then you have to compute the derivatives of it, which will give the tangential line at any point.
x'(t) = x1 + 2*x2*t + 3*x3*t*t
y'(t) = y1 + 2*y2*t + 3*y3*t*t
and the normal line at any point. The normal is the perpendicular at any point and the one which is the support of your two points.
(-y'(t), x'(t))
((y'(t), -(x'(t))

Related

Line to line intersection works only on one side

I'm creating a raycasting engine on javascript using p5js and there is an issue with the line to line (raycast to wall) intersection.
I found a lot of line to line collision algorithms, including p5 collide library, but the problem appears on every one of them.
this.intersects = function (raycastStart, raycastEnd) {
var x1 = this.startPoint.x; //Start point is the first point of a line.
var y1 = this.startPoint.y;
var x2 = this.endPoint.x; //End point is the second point of a line.
var y2 = this.endPoint.y;
var x3 = raycastStart.x;
var y3 = raycastStart.y;
var x4 = raycastEnd.x;
var y4 = raycastEnd.y;
var a_dx = x2 - x1;
var a_dy = y2 - y1;
var b_dx = x4 - x3;
var b_dy = y4 - y3;
var s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / (-b_dx * a_dy + a_dx * b_dy);
var t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / (-b_dx * a_dy + a_dx * b_dy);
//Vector2 is simply class with two fields: x and y.
return (s >= 0 && s <= 1 && t >= 0 && t <= 1) ? new Vector2(x1 + t * a_dx, y1 + t * a_dy) : null;
}
The line to line collision works on one side properly, but on the other, it works incorrect, according to my y position.
this is my map.
on one side it works perfectly
but on the other, it checks collision for line segments, that are lower than my Y position
(I would comment, but don't have enough reputation to do so...)
It appears that your line collision algorithm is working. But what appears to be missing is a check to determine which raycaster-to-line intersection is closer. That is, in your working example the raycast never casts across two line segments, so there is no question about which line segment constrains your raycast. But in your non-working example, the raycaster hits 2 of your 4 segments, so you now need to determine which of the 2 intersection points is closer to the raycast start, in order to determine which line segment is closer.

Rotating point by its form axe makes itself move to the left-top

Firstly, I need to rotate a single line about its own axe to be rendered in HTML5 canvas.I'm not actually rotating a line, but I'm rotating its points coordinates before I render it.
I've specific bounds which refers to the total size of what will be drawn in a path (e.g, the line), they're used to construct a point B, which is the center point of the path.
form.bx = x + (w / 2);
form.by = y + (h / 2);
Note: form is actually a path generated by my interface, it contains the rotation angle, etc.
Both of these functions rotates coordinates of a point and return them. Parameters:
x – the x coor. of the A point.
y – the y coor. of the A point.
c – the angle cosine (Math.cos(angle)).
s – the angle sine (Math.sin(angle)).
Bx – the x coor. of the point on which point A will be rotated about.
By – the y coor. of the point on which point A will be rotated about.
function rotatePointX(x, y, c, s, Bx, By) {
return Math.round(((Bx - x) * c) - ((By - y) * s));
}
function rotatePointY(x, y, c, s, Bx, By) {
return Math.round(((Bx - x) * s) + ((By - y) * c));
}
The problem: the rotation makes my line move to the left-top of the canvas.
Fiddle
My line should be in its specific position. What am I doing wrong? Thanks
Rotation always occurs relative to the origin, if you want to rotate relative to some other point you have to
move all your space so that the new origin is the point the rotation will occur about
perform the rotation
move all your space back to its original position
1)
x' = x - Bx
y' = y - By
2)
x'' = x' * cos(angle) - y' * sin(angle)
y'' = x' * sin(angle) + y' * cos(angle)
3)
x''' = x'' + Bx
y''' = y'' + By
To find the value of x''', y''' you have to replace 1 and 2 in 3
1 in 2:
x'' = (x - Bx) * cos(angle) - (y - By) * sin(angle)
y'' = (x - Bx) * sin(angle) + (y - By) * cos(angle)
2 in 3:
x''' = (x - Bx) * cos(angle) - (y - By) * sin(angle) + Bx
y''' = (x - Bx) * sin(angle) + (y - By) * cos(angle) + By
You can avoid all this mess by performing the transform doing matrix multiplication like so
[x'''] = [1 0 Bx] [cos(angle) -sin(angle) 0] [1 0 -Bx] [x]
[y'''] [0 1 By] [sin(angle) cos(angle) 0] [0 1 -By] [y]
[1 ] [0 0 1] [ 0 0 1] [0 0 1] [1]
The additional dimension is used to be able to perform translation since translation is a shearing mapping
https://jsfiddle.net/hnv0dcs6/2/

UserInput Is Messing Up a Function

I was trying to make something that told you the intersection points of two circles. Where I put in the centers of the circles and the radius. (I got the intersection function from stackoverflow: here). I am trying to add the user input but when ever I change the static number inside the code to a user input (either through prompt or html input), the function breaks and the alert sends me an unfinished answer and an Nan.
Here is the coding so far (without user input):
<html>
<button onclick="button()">Test</button>
<script>
var x0 = 3;
var y0 = 0;
var r0 = 3;
var x1 = -1;
var y1 = 0;
var r1 = 2;
function button() {
intersection(x0, y0, r0, x1, y1, r1)
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a/d);
y2 = y0 + (dy * a/d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0*r0) - (a*a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h/d);
ry = dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
var list = "(" + xi + ", " + yi + ")" + "(" + xi_prime + ", " +
yi_prime + ")"
alert(list);
}
}
</script>
</html>
When I change anyone of the variables that apart of the circle into user input like:
var x0 = prompt("X cord of circle 1");
The alert comes up as: (3-2.6250, -1.4523687548277813)(NaN, 1.4523687548277813)
and without the user input (shown in the large code block) it comes out as: (0.375, -1.4523687548277813)(0.375, 1.4523687548277813). Which is the correct answer.
Can anyone tell me what I am doing wrong or what is going on?
Prompt will take the user input as a string. To convert it to an integer for math jazz, use parseInt.
var x0String = prompt("X cord of circle 1");
var x0 = parseInt(x0String);
JavaScript should "convert" numeric string to integer if you perform calculations on it since JS is weakly typed, but it is good practice and you can avoid some pitfalls by parsing the integer value from a string yourself.
Your prompt returns a string, but you can't do math on a string. Try converting it to a number:
var x0 = Number(prompt("X cord of circle 1"));
As Daniel pointed out it's always better to change the string to a number if you need it as a number. It seemed really confusing, why the program was not working, until I found that x0 is used twice.
The reason the program was returning NaN is because when using the + operator, the number is converted into a string not a number.
That happens here: x0 + (dx * a/d);
What happens then is that a negative number is added to the string creating something like: 2-2
As you might expect the value can no longer be converted into a number, thus returning NaN, when we try to minus it later.

Detect mouse is near circle edge

I have a function which gets the mouse position in world space, then checks to see if the mouse is over or near to the circle's line.
The added complication how ever is the circle is transformed at an angle so it's more of an ellipse. I can't see to get the code to detect that the mouse is near the border of circle and am unsure where I am going wrong.
This is my code:
function check(evt){
var x = (evt.offsetX - element.width/2) + camera.x; // world space
var y = (evt.offsetY - element.height/2) + camera.y; // world space
var threshold = 20/scale; //margin to edge of circle
for(var i = 0; i < obj.length;i++){
// var mainAngle is related to the transform
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
var y1 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius + threshold) * mainAngle,2);
var x0 = Math.pow((x - obj[i].originX),2) / Math.pow((obj[i].radius - threshold) * 1, 2);
var y0 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius - threshold) * mainAngle, 2);
if(x1 + y1 <= 1 && x0 + y0 >= 1){
output.innerHTML += '<br/>Over';
return false;
}
}
output.innerHTML += '<br/>out';
}
To understand it better, I have a fiddle here: http://jsfiddle.net/nczbmbxm/ you can move the mouse over the circle, it should say "Over" when you are within the threshold of being near the circle's perimeter. Currently it does not seem to work. And I can't work out what the maths needs to be check for this.
There is a typo on line 34 with orignX
var x1 = Math.pow((x - obj[i].orignX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
should be
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
now you're good to go!
EDIT: In regards to the scaling of the image and further rotation of the circle, I would set up variables for rotation about the x-axis and y-axis, such as
var xAngle;
var yAngle;
then as an ellipse can be written in the form
x^2 / a^2 + y^2 / b^2 = 1
such as in Euclidean Geometry,
then the semi-major and semi-minor axes would be determined by the rotation angles. If radius is the circles actual radius. then
var semiMajor = radius * cos( xAngle );
var semiMinor = radius;
or
var semiMajor = radius;
var semiMinor = radius * cos( yAngle );
you would still need to do some more transformations if you wanted an x and y angle.
so if (xMouseC, yMouseC) are the mouse coordinates relative to the circles centre, all you must do is check if that point satisfies the equation of the ellipse to within a certain tolerance, i.e. plug in
a = semiMajor;
b = semiMinor;
x = xMouseC;
y = yMouseC;
and see if it is sufficiently close to 1.
Hope that helps!

How can we find intersection of circle and polygon for no point of polygon in circle

Suppose i have one polygon and one circle of google drawingmanager (google maps). The scenario is that no point of polygon is in the circle but still intersecting. How can i check the intersection in this case? ![Circle and Polygon are intersecting and there is no point of polygon inside the circle][1]
Check each point of polygon for this condition:
dx^2+dy^2 < r^2
where dx = px[i] - cx, dy = py[i] - cy,
px[i], py[i] - i-point of polygon, cx,cy - circle center, r - radius
If its true for at least one i, then intersection have place.
Update:
In case if no point is directly in circle, it's getting harder. You'll need to check each line against circle for intersections.
To detect intersection of line with circle, use following check:
line equation is l(t) = [lx(t), ly(t)]
where lx = x0 + t*(x1-x0), ly = y0 + t*(y1-y0), and t here is variable between 0 and 1
if line intersects circle, there will be such value of t (say, t0), with which
l0x = lx(t0), l0y = ly(t0) fills condition
(l0x - cx)^2 + (l0y - cy)^2 < r^2
we need to find t0, and check if it's in range 0..1, here how we do it:
(x0 + t0*(x1-x0) - cx)^2 + (y0 + t0*(y1-y0) - cy)^2 = r^2
by solving this quadratic equation we will find 0, 1 or 2 solutions of t0
if it's 0 solutions - circle never intersects line
if it's 1 solution - circle touches line in 1 point
if it's 2 solution (t0a, t0b) - circle intersects line in 2 points, and additional check will be needed to check if range [t0a, t0b] intersects with range [0, 1]
to solve this equation we need normalize it:
(x0 + t0*(x1-x0) - cx)^2 + (y0 + t0*(y1-y0) - cy)^2 = r^2 equal to
((x0-cx) + t0*(x1-x0))^2 + ((y0 - cy) + t0*(y1-y0))^2 - r^2 = 0 equal to
(x0-cx)^2 + (t0*(x1-x0))^2 + 2*t0*(x1-x0)*(x0-cx) + (y0-cy)^2 + (t0*(y1-y0))^2 + 2*t0*(y1-y0)*(y0-cy) - r^2 = 0 equal to
t0^2 * A + t0 * B + C = 0, where
A = (x1-x0)^2 + (y1-y0)^2
B = 2*(x1-x0)*(x0-cx) + 2*(y1-y0)*(y0-cy)
C = (x0-cx)^2 + (y0-cy)^2 - r^2
I will not write here how to solve such standart quadratic equation, it's offtopic.
If you will have a solution with 2 values, say t0a and t0b, then you need to check it against range [0, 1].
For example:
t0a = -3.4, t1a = 2.1 - intersection occurs,
t0a = -3.4, t1a = -2.1 - intersection not occurs,
t0a = 0, t1a = 0.5 - intersection occurs
Yeah, and probably you will need to sort t0a and t0b, there is no guaranty that t0a < t0b
You will need to run this check for each line of polygon.

Categories

Resources