What I'm trying to do is this:
I have two (x, y)points, p1 and p2, and a rotation value from p1 (angle in radians). P2 has also two other variables, a width and height, that I will call p2w and p2h. I want to check if the angle from p1 intersects the bounds of p2, in a radius of the width and/or height.
In other words, if the angle "cuts through" the square of center p2, width p2w and height p2h.
Here's a graph for better understanding:
http://i.imgur.com/Y7WFD36.png
What I've been trying to do is this:
if( p1.rot > (Math.atan2(p2.y-p2h, p2.x-p2w))
&& p1.rot < (Math.atan2(p2.y+p2h, p2.x+p2w)) )
//There's an intersection
But as you guess, it doesn't work as intended; is there another way to do this?
Captain Math to the rescue!
You are asking whether a ray intersects a rectangle. Here's what we need to do.
Firstly, a ray is defined using either a point and a vector or a point and angle. Since working with vectors is much easier, let's convert your angle to a vector. Using the Pythagorean theorem, your angle phi is identical to the vector n = {x: Math.cos(phi), y: Math.sin(phi)}
.
I'll rename your variables to make notation easier. I'll denote your p1 with p and your implicitly defined rectangle with r.
Now, for any random point M lying on the ray, the following equations must hold:
M.x = p.x + n.x * alpha (1)
M.y = p.y + n.y * alpha
for some real alpha. Similarly, for any random point M lying within the rectangle, the following inequalities must hold:
M.x >= r.x
M.x <= r.x + r.w
M.y >= r.y
M.y <= r.y + r.h
For a point to lie on both the ray and within the the rectangle both the equations and the inequalities must hold. Substituting the values above, we get:
p.x + n.x * alpha >= r.x
p.x + n.x * alpha <= r.x + r.w
p.y + n.y * alpha >= r.y
p.y + n.y * alpha <= r.y + r.h
Solve for alpha and we get:
alpha >= (r.x - p.x) / n.x
alpha <= (r.x + r.w - p.x) / n.x
alpha >= (r.y - p.y) / n.y
alpha <= (r.y + r.h - p.y) / n.y
The system above has a solution if and only if:
var lowerLimitX = (r.x - p.x) / n.x;
var lowerLimitY = (r.y - p.y) / n.y;
var upperLimitX = (r.x + r.w - p.x) / n.x;
var upperLimitY = (r.y + r.h - p.y) / n.y;
var minAlpha = Math.max(lowerLimitX, lowerLimitY);
var maxAlpha = Math.min(upperLimitX, upperLimitY);
var hasSolution = minAlpha<= maxAlpha;
Now, if the system above has a solution, it must be the case that at least one point lies on both the ray and the rectangle, in other words, they intersect.
Edit: Here's a working demo . Move the mouse around to see the results. Note that, because of the fact that the Y axis grows downwards in the HTML canvas API, one must swap the lower and upper limits of the Y axis.
Edit 2 : If you care about the intersection segment as suggested by @pfannkuchen_gesicht (note that generally, the intersection will be a line segment, not a point), well that's easy as well. As we already know, for the points on the intersection, the ray equations must hold. To find the points themselves, simply substitute alpha with a value within the range [minAlpha; maxAlpha] in (1). For example, the closest point is p + minAlpha * n
, the farthest is p + maxAlpha * n
and a random point in between is p +(minAlpha + Math.random() * (maxAlpha - minAlpha)) * n
.
Here's one way to test if your line segment (ray?) and rectangle intersect.
Just test if the line segment / ray intersects either of the 2 diagonals of the rectangle.
Example code and a Demo:
var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); var cw=canvas.width; var ch=canvas.height; ctx.lineWidth=3; function reOffset(){ var BB=canvas.getBoundingClientRect(); offsetX=BB.left; offsetY=BB.top; } var offsetX,offsetY; reOffset(); window.onscroll=function(e){ reOffset(); } var ray1={x:30,y:250,angle:-Math.PI/3.5}; var ray2={x:30,y:250,angle:-Math.PI/6}; var r={x:100,y:100,w:40,h:40}; ctx.strokeStyle='black'; ctx.strokeRect(rx,ry,rw,rh); // this ray intersects the rect drawRay(ray1,r); // this ray doesn't intersect the rect drawRay(ray2,r); function drawRay(ray,rect){ var intersects=rayRectIntersect(ray,rect); ctx.beginPath(); ctx.moveTo(ray.x,ray.y); ctx.lineTo(ray.x+1000*Math.cos(ray.angle),ray.y+1000*Math.sin(ray.angle)); ctx.strokeStyle=(intersects)?'red':'green'; ctx.stroke(); } function rayRectIntersect(ray,rect){ d0={x:rect.x,y:rect.y}; d1={x:rect.x+rect.w,y:rect.y+rect.h}; d2={x:rect.x,y:rect.y+rect.h}; d3={x:rect.x+rect.w,y:rect.y}; ray0={x:ray.x,y:ray.y}; ray1={x:ray.x+1000*Math.cos(ray.angle),y:ray.y+1000*Math.sin(ray.angle)}; var diag1Test=line2lineIntersection(ray0,ray1,d0,d1); var diag2Test=line2lineIntersection(ray0,ray1,d2,d3); return(diag1Test || diag2Test); } // Get interseting point of 2 line segments (if any) // Attribution: http://paulbourke.net/geometry/pointlineplane/ function line2lineIntersection(p0,p1,p2,p3) { var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x); var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x); var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y); // Test if Coincident // If the denominator and numerator for the ua and ub are 0 // then the two lines are coincident. if(unknownA==0 && unknownB==0 && denominator==0){return(true);} // Test if Parallel // If the denominator for the equations for ua and ub is 0 // then the two lines are parallel. if (denominator == 0) return false; // If the intersection of line segments is required // then it is only necessary to test if ua and ub lie between 0 and 1. // Whichever one lies within that range then the corresponding // line segment contains the intersection point. // If both lie within the range of 0 to 1 then // the intersection point is within both line segments. unknownA /= denominator; unknownB /= denominator; var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1) if(!isIntersecting){return(false);} return({ x: p0.x + unknownA * (p1.x-p0.x), y: p0.y + unknownA * (p1.y-p0.y) }); }
body{ background-color: ivory; } #canvas{border:1px solid red;}
<h4>Ray is red if intersecting, green if not</h4> <canvas id="canvas" width=300 height=300></canvas>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.