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.
Related
I have a canvas with this params:
width = 400, height = 400
and have a line passing through the point cursor[x1,y1] at an angle Q (in degree)
I need get all coords of the intersection of the line in the plane and write it to array. Now i use this equation: y - y1 = k * (x - x1)
to check all point I use this code:
var rad = Q * Math.PI/180;
for (ctrY = 0; ctrY < 400; ctrY += 1) {
for (ctrX = 0; ctrX < 400; ctrX += 1) {
if ( (ctrY - cursor.y) ===
~~(Math.tan(rad) * (ctrX - cursor.x)) ) {
z.push([ctrX, ctrY]);
}
}
}
For example when 0 < Q < 90 and cursor[x1,y1] = [200,200] z.length = 0 and it's not correct.
Where i'm wrong? Maybe there is a more convenient algorithm?
P.S. Sorry for my english
Seems you need line rastering algorithm. Consider Bresenham algorithm.
You can also look at DDA algorithm
I imagine an algorithm like this. (I only consider the case when 0 < Q < 90). First I will want to calculate the points where the line will intersect the Ox and Oy axes, considering the origin (0,0) point the upper left corner and if we imagine that the negative x and y values are respectively to the left and to the top of this point. Let x2 and y2 be the values where the line will intersect Ox and Oy. We want to calculate these values. We now have a system with 2 unknown variables (x2 and y2): Math.tan(rad) = (y1 -y2)/x1 and Math.tan(rad) = y1/(x1-x2). We can deduct these equations by drawing the line on the coordinate system and analyzing a bit. If we solve the system of equations we find something like: x2 = (x1*y1 -x1 * x1 * Math.tan(rad)/(2 * y1-x1)) and y2= y1- x1 * Math.tan(rad) (These need to be verified, I haven't double checked my calculus). A linear equation can be defined by the formula y = a*x + b and in our case a = x2 and b = y2. We can then calculate the points like this:
for (xIdx = 0; xIdx < 400; xIdx += 1) {
var ctrX = xIdx;
var ctrY = x2 * ctrX + y2 //todo: replace with the respective calculated variables x2 and y2(we could also define two functions in js) and proper rounding
z.push([ctrX, ctrY]);
}
I'm not sure if I'm 100% accurate but I hope you understand my idea.
I'm trying to make a project where the user can draw arrows in a canvas and i need a curved line for that.
As you know one quadratic curve is represented by something like that:
M 65 100 Q 300, 100, 300, 20
Where the first two numbers(65, 100) represents the starting point coordinates, the last two (300,20) represents the ending point coordinates(arrow end).
I need to calculate the middle two numbers based on the first and second point, to make a nice looking curved line.
The first point will have the coordinates from mousedown and the second point from mouseup.
For now i'm using like this.
function addCurve(Ax, Ay, Bx, By){
canvas.add(new fabric.Path('M '+ Ax +' '+ Ay +' Q 100, 100, '+ Bx +', '+ By +'', { fill: '', stroke: 'red' }));
}
addCurve(100,0,200,0);
So, how to calculate the middle point coordinates to get an uniform curve?
I'm also using fabric.js in this project.
First start with the two end points
x1 = ? // start point
y1 = ?
x2 = ? // end point
y2 = ?
To get the mid point
mx = (x1 + x2) / 2;
my = (y1 + y2) / 2;
You will need the vector from first to second point
vx = x2 - x1;
vy = y2 - y1;
The line at 90deg (clockwise or right) from the start and end points is
px = -vy; // perpendicular
py = vx;
The line is the same length as the distance between the two points. The quadratic curve will extend out half the distance that the control point is from the line. So if we want the curve to be 1/4 out by length then half the p vector and add to mid point
cx = mx + px / 2; // get control point
cy = my + py / 2;
If you want the curve to bend the other way
cx = my - px / 2;
cy = my - py / 2;
Or you can write it with the curve amount as a var
var curveAmount = 0.25; // How far out the curve is compared to the line length
cx = my - px * (curveAmount * 2);
cy = my - py * (curveAmount * 2);
Make curveAmount bigger for more curve, smaller for less. Zero for no curve at all and negative to bend the other way.
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.
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!
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))