简体   繁体   中英

Check if a vector/angle intersects area

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM