Rotating object relative to mouse position - graphics

At the moment I'm using the dot product of the mouse position and (0, 1) to generate radians to rotate an object, in three.js
Code below, works ok but the object 'jumps' because the radian angle skips from positive to negative when the clientX value goes between window.innerWidth / 2
onDocumentMouseMove : function(event) {
// rotate circle relative to current mouse pos
var oldPos = new THREE.Vector2(0, 1);
Template.Main.mouseCurrPos = new THREE.Vector2((event.clientX / window.innerWidth ) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
Template.Main.mouseCurrPos.normalize();
//Template.Main.projector.unprojectVector(Template.Main.mouseCurrPos, Template.Main.scene);
var angle = oldPos.dot(Template.Main.mouseCurrPos);
Template.Main.mousePrevPos.x = event.clientX;
Template.Main.mousePrevPos.y = event.clientY;
if (event.clientX < window.innerWidth / 2) {
Template.Main.circle.rotation.z = -angle;
}
else {
Template.Main.circle.rotation.z = angle;
}
console.log(Template.Main.circle.rotation.z);
}
However if I add this to assign the value to oldPos:
if (event.clientX < window.innerWidth / 2) {
oldPos = new THREE.Vector2(0, -1);
}
else {
oldPos = new THREE.Vector2(0, 1);
}
Then the "jumping" goes but the effect of rotation is inverted when the mouse is on the left of the window.
I.e. mouse going up rotates anti-clockwise and vice-versa which is not desired.
It's frustrating.
Also if I keep the oldPos conditional assignment and leave out the conditional negation of the angle instead, the jumping comes back.
You can see a demo here: http://theworldmoves.me/rotation-demo/
Many thanks for any tips.

Why are you using the result of the dot product as the angle (radians)? The dot product gives you the cosine of the angle (times the magnitude of the vectors, but these are a unit vector and a normalized vector, so that doesn't matter).
You could change your angle computation to
var angle = Math.acos(oldPos.dot(Template.Main.mouseCurrPos));
However, you may get the wrong quadrant, since there can be two values of theta that satisfy cos(theta) = n. The usual way to get the angle of a vector (origin to mouse position) in the right quadrant is to use atan2():
var angle = Math.atan2(Template.Main.mouseCurrPos.y,
Template.Main.mouseCurrPos.x);
This should give the angle of the mouse position vector, going counterclockwise from (1, 0). A little experimentation can determine for sure where the zero angle is, and which direction is positive rotation.

Related

Constructing a Line in the Vertex Shader - Removing Perspective Scaling

I'm trying to implement a line renderer that expands the line vertices in the vertex shader, so that they expand in screen space, so that all segments of lines are exactly the same size, regardless of how far they are from the camera, each other, or the origin.
I first tried implementing my own version, but could not seem to cancel out the perspective scaling that seems to happen automatically in the graphics pipeline. I then adapted some components from this website: https://mattdesl.svbtle.com/drawing-lines-is-hard (see Screen-Space Projected Lines section). See their full vertex shader here: https://github.com/mattdesl/webgl-lines/blob/master/projected/vert.glsl
In my version, the c++ code uploads two vertices for each end of the line, along with a direction vector pointing from line point A to B, and a scalar sign (-1 or +1) used to expand the line in opposite perpendicular directions, to give it thickness. The vertex shader then constructs two screen space coordinates, generates a screen space direction, then generates a perpendicular direction (using the signed scalar) from that.
In the website's code, they upload 3 positions (prev, cur, next) - I believe so that they can generate joints. But in my case, I just want a simple segment, so I upload the current position, along with a world-space direction to the next position (all vertices of a segment get the same world space line direction). Then in my vertex shader, I construct the "next world position" by adding the world line direction to the current world/vertex position, then transform both into screen space. I probably could have just transformed the world space direction into screen space, but I'm currently trying to rule out all sources of unknowns.
Here is the code I have so far. I believe I've transformed and scaled my vectors just as they have, but my lines are still scaling as they change depths. I'm not sure if I've missed something from the web-page, or if this is the result they were after. But since they are dividing their projected xy coordinates by their projected w coordinate, it sure seems like they were trying to cancel out the scaling.
The closest I've came to achieving the result I want (constant thickness) was to override the w component of all projected positions with the Scene.ViewProj[3][3] component. It almost seemed to work that way, but there was still some strange scaling when the view was rotated. Anyway, here is the code trying to emulate the logic from the website. Any advice on how to make this work would be very much appreciated:
struct sxattrScene
{
float4x4 Eye; // world space transform of the camera
float4x4 View; // view transform - inverted camera
float4x4 Proj; // projection transform for camera perspective/FOV
float4x4 ViewProj; // view * projection transform for camera
float4x4 Screen; // screen projection transform for 2D blitting
float2 Display; // size of display
float Aspect; // aspect ratio of display sizes
float TimeStep; // time that advanced from last frame to this one, in milliseconds
};
ConstantBuffer<sxattrScene> Scene; // constant buffer scene
// input vertex
struct vinBake
{
// mesh inputs
float4 Position : ATTRIB0; // world position of the center of the line (2 verts at each end)
float4 Color : ATTRIB1; // color channels
float3 TexCoord : ATTRIB2; // x=sign, y=thickness, z=feather
// enhanced logic
float4 Prop : ATTRIB3; // xyz contains direction of line (from end points A -> B)
float4 Attr : ATTRIB4; // not used here
};
// 3D line drawing interpolator
struct lerpLine3D
{
float4 ClipPos : SV_POSITION; // projected clip-space screen position of vertex
float4 Diffuse : COLOR0; // diffuse color
float3 ScrPos : TEXCOORD0; // screen-space position of this point
float Factor : TEXCOORD1; // factor value of this position (0->1)
float Feather : TEXCOORD2; // falloff of line
};
// vertex shader
lerpLine3D vs(vinBake vin)
{
// prepare output
lerpLine3D lerp;
// float ww = Scene.ViewProj[3][3];
// generate projected screen position
lerp.ClipPos = mul( Scene.ViewProj, float4( vin.Position.xyz, 1.0) );
// generate a fake "next position" using the line direction, then transform into screen space
float4 next_proj = mul( Scene.ViewProj, float4( vin.Position.xyz + vin.Prop.xyz, 1.0) );
// remove perspect from both positions
float2 curr_screen = lerp.ClipPos.xy / lerp.ClipPos.w;
float2 next_screen = next_proj.xy / next_proj.w;
// correct for aspect ratio
curr_screen.x *= Scene.Aspect;
next_screen.x *= Scene.Aspect;
// generate a direction between these two screen positions
float2 dir = normalize( next_screen - curr_screen );
// extract sign direction .. -1 (neg side) to +1 (pos side)
float sign = vin.TexCoord.x;
// extract line size
float thickness = vin.TexCoord.y;
// extract alpha falloff (used in pixel shader)
lerp.Feather = vin.TexCoord.z;
// remap sign (-1 to +1) into line factor (0 to 1) - used in ps
lerp.Factor = ( sign + 1.0 ) * 0.5;
// compute our expanse, defining how far to push our line vertices out from the starting center point
float expanse = thickness * sign;
// compute our offset vector
float4 offset = float4( -dir.y * expanse / Scene.Aspect, dir.x * expanse, 0.0, 1.0 );
// push our projected position by this offset
lerp.ClipPos += offset;
// copy diffuse color
lerp.Diffuse = vin.Color;
// return lerp data
return lerp;
}
// compute a slope for the alpha falloff of a line draw
float ComputeLineAlpha(float t,float feather)
{
// slope feather to make it more useful
float ft = 1.0 - feather;
float ft4 = ft*ft*ft*ft;
// compute slope
return min( 1.0, t * 40.0 * ( 1.0 - t ) * ( 0.1 + ft4 ) );
}
// pixel shader
float4 ps(lerpLine3D lerp) : SV_TARGET
{
// compute line slope alpha
float alpha = ComputeLineAlpha( lerp.Factor, lerp.Feather );
// return the finished color while scaling the curve with alpha
return float4( lerp.Diffuse.rgb, lerp.Diffuse.a * alpha );
}
Edit:
I think I'm really close to figuring this out. I have things setup so that the lines are scaled correctly as long as all parts of a visible line are in front of the camera. Here is the updated vertex shader code, which is simpler than before:
lerpLine3D main(vinBake vin)
{
// prepare output
lerpLine3D lerp;
// generate projected screen position
lerp.ClipPos = mul( Scene.ViewProj, float4( vin.Position.xyz, 1.0 ) );
// generate fake clip-space point in the direction of the line
// + vin.Prop.xyz contains the world space direction of the line itself (A->B)
float4 next_proj = mul( Scene.ViewProj, float4( vin.Position.xyz + vin.Prop.xyz, 1.0 ) );
// generate a directiion between these two screen positions
float2 dir = normalize( next_proj.xy - lerp.ClipPos.xy );
// extract sign direction .. -1 (neg side) to +1 (pos side)
float sign = vin.TexCoord.x;
// extract line size from input
float thickness = vin.TexCoord.y;
// extract alpha falloff from input
lerp.Feather = vin.TexCoord.z;
// remap sign (-1 to +1) into line factor (0 to 1)
lerp.Factor = ( sign + 1.0 ) * 0.5;
// compute our expanse, defining how far to push our line vertices out from the starting center point
float expanse = thickness * sign;
// compute our offset vector
float2 offset = float2( -dir.y * expanse, dir.x * expanse * Scene.Aspect );
lerp.ClipPos.xy += offset * abs( lerp.ClipPos.w * 0.001 ); // <----- important part
// copy diffuse color
lerp.Diffuse = vin.Color;
// return lerp data
return lerp;
}
However, there is one serious problem I could use some help with, if anyone knows how to pull it off. Notice the updated code above that has the "important part" comment. The reason I placed an abs() here is because sometimes the end-points of a single line segment can cross through the camera/screen plane. In fact, this is pretty common, when drawing long lines, such as for a grid.
Also notice the 0.001 on that same line, which is an arbitrary number that I plugged in to make the scale similar to pixel scaling. But I'm pretty sure there is an exact way to calculate this scaling that will take things into account, such as lines crossing the screen plane.
The updated code above seems to work really well as long as both ends of the line segment are in front of the camera. But when one end is behind the camera, the line is expanded incorrectly. My understanding of the w component and perspective scaling is very limited, beyond knowing that things that are further away are smaller. The w component seems to be heavily derived from the 'z'/depth component after transforming into clip space, but I'm not sure what its min/max range would be under normal 3D circumstances. I'm wondering if just having the correct scaler in that line of code might fix the problem - something like this:
lerp.ClipPos.xy += offset * ((lerp.ClipPos.w-MIN_W_VALUE)/ENTIRE_W_RANGE);
But I'm honestly not familiar with these concepts enough to figure this out. Would anyone be able to point me in the right direction?
Edit: Well, in my engine at least, the w component seems to literally just be world-space depth, relative to the camera. So if something is 100 units in front of the camera, its w value will be 100. And if -100 units behind the camera, then it will be -100. Unfortunately, that seems like it would then have no range to lock it into. So its possible I'm going about this the wrong way. Anyway, would really appreciate any advice.

Find intersection point ray/triangle in a right-hand coordinate system

I would like to get the intersection point of a line (defined by a vector and origin) on a triangle.
My engine use right handed coordinate system, so X pointing forward, Y pointing left and Z pointing up.
---- Edit ----
With Antares's help, I convert my points to engine space with:
p0.x = -pt0.y;
p0.y = pt0.z;
p0.z = pt0.x;
But I don't know how to do the same with the direction vector.
I use the function from this stackoverflow question, original poster use this tutorial.
First we look for the distance t from origin to intersection point, in order to find its coordinates.
But I've got a negative t, and code return true when ray is outside the triangle. I set it outside visualy.
It return sometime false when I'm in the triangle.
Here is the fonction I use to get the intersection point, I already checked that it works, with 'classic' values, as in the original post.
float kEpsilon = 0.000001;
V3f crossProduct(V3f point1, V3f point2){
V3f vector;
vector.x = point1.y * point2.z - point2.y * point1.z;
vector.y = point2.x * point1.z - point1.x * point2.z;
vector.z = point1.x * point2.y - point1.y * point2.x;
return vector;
}
float dotProduct(V3f dot1, V3f dot2){
float dot = dot1.x * dot2.x + dot1.y * dot2.y + dot1.z * dot2.z;
return dot;
}
//orig: ray origin, dir: ray direction, Triangle vertices: p0, p1, p2.
bool rayTriangleIntersect(V3f orig, V3f dir, V3f p0, V3f p1, V3f p2){
// compute plane's normal
V3f p0p1, p0p2;
p0p1.x = p1.x - p0.x;
p0p1.y = p1.y - p0.y;
p0p1.z = p1.z - p0.z;
p0p2.x = p2.x - p0.x;
p0p2.y = p2.y - p0.y;
p0p2.z = p2.z - p0.z;
// no need to normalize
V3f N = crossProduct(p0p1, p0p2); // N
// Step 1: finding P
// check if ray and plane are parallel ?
float NdotRayDirection = dotProduct(N, dir); // if the result is 0, the function will return the value false (no intersection).
if (fabs(NdotRayDirection) < kEpsilon){ // almost 0
return false; // they are parallel so they don't intersect !
}
// compute d parameter using equation 2
float d = dotProduct(N, p0);
// compute t (equation P=O+tR P intersection point ray origin O and its direction R)
float t = -((dotProduct(N, orig) - d) / NdotRayDirection);
// check if the triangle is in behind the ray
//if (t < 0){ return false; } // the triangle is behind
// compute the intersection point using equation
V3f P;
P.x = orig.x + t * dir.x;
P.y = orig.y + t * dir.y;
P.z = orig.z + t * dir.z;
// Step 2: inside-outside test
V3f C; // vector perpendicular to triangle's plane
// edge 0
V3f edge0;
edge0.x = p1.x - p0.x;
edge0.y = p1.y - p0.y;
edge0.z = p1.z - p0.z;
V3f vp0;
vp0.x = P.x - p0.x;
vp0.y = P.y - p0.y;
vp0.z = P.z - p0.z;
C = crossProduct(edge0, vp0);
if (dotProduct(N, C) < 0) { return false; }// P is on the right side
// edge 1
V3f edge1;
edge1.x = p2.x - p1.x;
edge1.y = p2.y - p1.y;
edge1.z = p2.z - p1.z;
V3f vp1;
vp1.x = P.x - p1.x;
vp1.y = P.y - p1.y;
vp1.z = P.z - p1.z;
C = crossProduct(edge1, vp1);
if (dotProduct(N, C) < 0) { return false; } // P is on the right side
// edge 2
V3f edge2;
edge2.x = p0.x - p2.x;
edge2.y = p0.y - p2.y;
edge2.z = p0.z - p2.z;
V3f vp2;
vp2.x = P.x - p2.x;
vp2.y = P.y - p2.y;
vp2.z = P.z - p2.z;
C = crossProduct(edge2, vp2);
if (dotProduct(N, C) < 0) { return false; } // P is on the right side;
return true; // this ray hits the triangle
}
My problem is I get t: -52.603783
intersection point P : [-1143.477295, -1053.412842, 49.525799]
This give me, relative to a 640X480 texture, the uv point: [-658, 41].
Probably because my engine use Z pointing up?
My engine use right handed coordinate system, so X pointing forward, Y pointing left and Z pointing up.
You have a slightly incorrect idea of a right handed coordinate system... please check https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions.
As the name suggests, X is pointing right (right hand's thumb to the right), Y is pointing up (straight index finger) and Z (straight middle finger) is pointing "forward" (actually -Z is forward, and Z is backward in the camera coordinate system).
Actually... your coordinate components are right hand sided, but the interpretation as X is forward etc. is unusual.
If you suspect the problem could be with the coordinate system of your engine (OGRE maybe? plain OpenGL? Or something selfmade?), then you need to transform your point and direction coordinates into the coordinate system of your algorithm. The algorithm you presented works in camera coordinate system, if I am not mistaken. Of course you need to transform the resulting intersection point back to the interpretation you use in the engine.
To turn the direction of a vector component around (e.g. the Z coordinate) you can use multiplication with -1 to achieve the effect.
Edit:
One more thing: I realized that the algorithm uses directional vectors as well, not just points. The rearranging of components does only work for points, not directions, if I recall correctly. Maybe you have to do a matrix multiplication with the CameraView transformation matrix (or its inverse M^-1 or was it the transpose M^T, I am not sure). I can't help you there, I hope you can figure it out or just do trial&error.
My problem is I get t: -52.603783
intersection point P : [-1143.477295, -1053.412842, 49.525799] This give me, relative to a 640X480 texture, the uv point: [-658, 41]
I reckon you think your values are incorrect. Which values do you expect to get for t and UV coordinates? Which ones would be "correct" for your input?
Hope this gets you started. GL, HF with your project! :)
#GUNNM: Concerning your feedback that you do not know how to handle the direction vector, here are some ideas that might be useful to you.
As I said, there should be a matrix multiplication way. Look for key words like "transforming directional vector with a matrix" or "transforming normals (normal vectors) with a matrix". This should yield something like: "use the transpose of the used transformation matrix" or "the inverse of the matrix" or something like that.
A workaround could be: You can "convert" a directional vector to a point, by thinking of a direction as "two points" forming a vector: A starting point and another point which lies in the direction you want to point.
The starting point of your ray, you already have available. Now you need to make sure that your directional vector is interpreted as "second point" not as "directional vector".
If your engine handles a ray like in the first case you would have:
Here is my starting point (0,0,0) and here is my directional vector (5,6,-7) (I made those numbers up and take the origin as starting point to have a simple example). So this is just the usual "start + gaze direction" case.
In the second case you would have:
Here is my start at (0,0,0) and my second point is a point on my directional vector (5,6,-7), e.g. any t*direction. Which for t=1 should give exactly the point where your directional vector is pointing to if it is considered a vector (and the start point being the origin (0,0,0)).
Now you need to check how your algorithm is handling that direction. If it does somewhere ray=startpoint+direction, then it interprets it as point + vector, resulting in a movement shift of the starting point while keeping the orientation and direction of the vector.
If it does ray=startpoint-direction then it interprets it as two points from which a directional vector is formed by subtracting.
To make a directional vector from two points you usually just need to subtract them. This gives a "pure direction" though, without defined orientation (which can be +t or -t). So if you need this direction to be fixed, you may take the absolute of your "vector sliding value" t in later computations for example (may be not the best/fastest way of doing it).

Drawing a circle with a specific start point (from degree view)

the drawing algorithm which I currently use:
a_max = Pi*2 (float)(num_segments - 1.0f)/(float)num_segments;
for (unsigned int i = 0; i<=num_segments;i++)
{
const float a = (float)i / (float)num_segments * a_max;
SetPixel(centre.x + cos(a) *radius, centre.y +sin(a) *radius);
}
Works fine, but it starts drawing at (centre.x+radius, centre.y). I would like to have it to start at the top , because I want to draw a compass and zero degree is at the top, not on the right, so that I don't have to make a hacky solution.
Try rotating 90 degrees to the left before you start drawing, this should solve it for you.
A compass not only starts at "north" instead of "east" but also goes clockwise instead of counter-clockwise.
For this case, just swap sin(a) and cos(a):
x = centre.x + sin(a) * radius
y = centre.y + cos(a) * radius

mfc, can any one help with an algorithm for airbrush, i just can't understand how to do it

Is there any way to fill an ellipse or a rect by point to point like in an airbrush tool in mspaint?
I could not find a way to create an empty rect or an ellipse and then fill them up pixel by pixel or setting random pixels on screen in a circle way....
Can i tell setPixel to fill inside a dcellipse or anything like that?
10x
You need to create a region with CRgn, then select that as the clipping region in your CDC with SelectClipRgn. Then you can use CDC::SetPixel to set random pixels anywhere within the bounding rectangle of your shape, and only the ones within the clipping region will be painted.
Be aware that this will be slow, and will need to be redone every time the window paints (such as when another window is dragged over it).
In your "make random pixels" loop, just exclude the pixel if it's outside your desired circle.
num_pixels = 20; // how many pixels
circle_radius = 32; // 32-pixel radius, or whatever you'd like
circle_radius2 = circle_radius * circle_radius;
while (num_pixels-- > 0)
{
// get a random number between (-circle_radius / 2, circle_radius / 2)
pixel_x = rand(circle_radius) - circle_radius / 2;
pixel_y = rand(circle_radius) - circle_radius / 2;
// compute squared distance between generated pixel and radius,
// exclude if out of range
if ( (center_x - pixel_x) * (center_x - pixel_x) +
(center_y - pixel_y) * (center_y - pixel_y) > circle_radius2 )
continue; // generate another pixel
// do stuff with pixel
}

Circle-Rectangle collision detection (intersection)

How can I tell whether a circle and a rectangle intersect in 2D Euclidean space? (i.e. classic 2D geometry)
Here is how I would do it:
bool intersects(CircleType circle, RectType rect)
{
circleDistance.x = abs(circle.x - rect.x);
circleDistance.y = abs(circle.y - rect.y);
if (circleDistance.x > (rect.width/2 + circle.r)) { return false; }
if (circleDistance.y > (rect.height/2 + circle.r)) { return false; }
if (circleDistance.x <= (rect.width/2)) { return true; }
if (circleDistance.y <= (rect.height/2)) { return true; }
cornerDistance_sq = (circleDistance.x - rect.width/2)^2 +
(circleDistance.y - rect.height/2)^2;
return (cornerDistance_sq <= (circle.r^2));
}
Here's how it works:
The first pair of lines calculate the absolute values of the x and y difference between the center of the circle and the center of the rectangle. This collapses the four quadrants down into one, so that the calculations do not have to be done four times. The image shows the area in which the center of the circle must now lie. Note that only the single quadrant is shown. The rectangle is the grey area, and the red border outlines the critical area which is exactly one radius away from the edges of the rectangle. The center of the circle has to be within this red border for the intersection to occur.
The second pair of lines eliminate the easy cases where the circle is far enough away from the rectangle (in either direction) that no intersection is possible. This corresponds to the green area in the image.
The third pair of lines handle the easy cases where the circle is close enough to the rectangle (in either direction) that an intersection is guaranteed. This corresponds to the orange and grey sections in the image. Note that this step must be done after step 2 for the logic to make sense.
The remaining lines calculate the difficult case where the circle may intersect the corner of the rectangle. To solve, compute the distance from the center of the circle and the corner, and then verify that the distance is not more than the radius of the circle. This calculation returns false for all circles whose center is within the red shaded area and returns true for all circles whose center is within the white shaded area.
There are only two cases when the circle intersects with the rectangle:
Either the circle's centre lies inside the rectangle, or
One of the edges of the rectangle has a point in the circle.
Note that this does not require the rectangle to be axis-parallel.
(One way to see this: if none of the edges has a point in the circle (if all the edges are completely "outside" the circle), then the only way the circle can still intersect the polygon is if it lies completely inside the polygon.)
With that insight, something like the following will work, where the circle has centre P and radius R, and the rectangle has vertices A, B, C, D in that order (not complete code):
def intersect(Circle(P, R), Rectangle(A, B, C, D)):
S = Circle(P, R)
return (pointInRectangle(P, Rectangle(A, B, C, D)) or
intersectCircle(S, (A, B)) or
intersectCircle(S, (B, C)) or
intersectCircle(S, (C, D)) or
intersectCircle(S, (D, A)))
If you're writing any geometry you probably have the above functions in your library already. Otherwise, pointInRectangle() can be implemented in several ways; any of the general point in polygon methods will work, but for a rectangle you can just check whether this works:
0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD
And intersectCircle() is easy to implement too: one way would be to check if the foot of the perpendicular from P to the line is close enough and between the endpoints, and check the endpoints otherwise.
The cool thing is that the same idea works not just for rectangles but for the intersection of a circle with any simple polygon — doesn't even have to be convex!
Here is another solution that's pretty simple to implement (and pretty fast, too). It will catch all intersections, including when the sphere has fully entered the rectangle.
// clamp(value, min, max) - limits value to the range min..max
// Find the closest point to the circle within the rectangle
float closestX = clamp(circle.X, rectangle.Left, rectangle.Right);
float closestY = clamp(circle.Y, rectangle.Top, rectangle.Bottom);
// Calculate the distance between the circle's center and this closest point
float distanceX = circle.X - closestX;
float distanceY = circle.Y - closestY;
// If the distance is less than the circle's radius, an intersection occurs
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < (circle.Radius * circle.Radius);
With any decent math library, that can be shortened to 3 or 4 lines.
The simplest solution I've come up with is pretty straightforward.
It works by finding the point in the rectangle closest to the circle, then comparing the distance.
You can do all of this with a few operations, and even avoid the sqrt function.
public boolean intersects(float cx, float cy, float radius, float left, float top, float right, float bottom)
{
float closestX = (cx < left ? left : (cx > right ? right : cx));
float closestY = (cy < top ? top : (cy > bottom ? bottom : cy));
float dx = closestX - cx;
float dy = closestY - cy;
return ( dx * dx + dy * dy ) <= radius * radius;
}
And that's it! The above solution assumes an origin in the upper left of the world with the x-axis pointing down.
If you want a solution to handling collisions between a moving circle and rectangle, it's far more complicated and covered in another answer of mine.
your sphere and rect intersect IIF
the distance between the circle-center and one vertex of your rect is smaller than the radius of your sphere
OR
the distance between the circle-center and one edge of your rect is smaller than the radius of your sphere ([point-line distance ])
OR
the circle center is inside the rect
point-point distance:
P1 = [x1,y1]
P2 = [x2,y2]
Distance = sqrt(abs(x1 - x2)+abs(y1-y2))
point-line distance:
L1 = [x1,y1],L2 = [x2,y2] (two points of your line, ie the vertex points)
P1 = [px,py] some point
Distance d = abs( (x2-x1)(y1-py)-(x1-px)(y2-y1) ) / Distance(L1,L2)
circle center inside rect:
take an seperating axis aproach: if there exists a projection onto a line that seperates the rectangle from the point, they do not intersect
you project the point on lines parallel to the sides of your rect and can then easily determine if they intersect. if they intersect not on all 4 projections, they (the point and the rectangle) can not intersect.
you just need the inner-product ( x= [x1,x2] , y = [y1,y2] , x*y = x1*y1 + x2*y2 )
your test would look like that:
//rectangle edges: TL (top left), TR (top right), BL (bottom left), BR (bottom right)
//point to test: POI
seperated = false
for egde in { {TL,TR}, {BL,BR}, {TL,BL},{TR-BR} }: // the edges
D = edge[0] - edge[1]
innerProd = D * POI
Interval_min = min(D*edge[0],D*edge[1])
Interval_max = max(D*edge[0],D*edge[1])
if not ( Interval_min ≤ innerProd ≤ Interval_max )
seperated = true
break // end for loop
end if
end for
if (seperated is true)
return "no intersection"
else
return "intersection"
end if
this does not assume an axis-aligned rectangle and is easily extendable for testing intersections between convex sets.
This is the fastest solution:
public static boolean intersect(Rectangle r, Circle c)
{
float cx = Math.abs(c.x - r.x - r.halfWidth);
float xDist = r.halfWidth + c.radius;
if (cx > xDist)
return false;
float cy = Math.abs(c.y - r.y - r.halfHeight);
float yDist = r.halfHeight + c.radius;
if (cy > yDist)
return false;
if (cx <= r.halfWidth || cy <= r.halfHeight)
return true;
float xCornerDist = cx - r.halfWidth;
float yCornerDist = cy - r.halfHeight;
float xCornerDistSq = xCornerDist * xCornerDist;
float yCornerDistSq = yCornerDist * yCornerDist;
float maxCornerDistSq = c.radius * c.radius;
return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
}
Note the order of execution, and half the width/height is pre-computed. Also the squaring is done "manually" to save some clock cycles.
Actually, this is much more simple. You need only two things.
First, you need to find four orthogonal distances from the circle centre to each line of the rectangle. Then your circle will not intersect the rectangle if any three of them are larger than the circle radius.
Second, you need to find the distance between the circle centre and the rectangle centre, then you circle will not be inside of the rectangle if the distance is larger than a half of the rectangle diagonal length.
Good luck!
Here's my C code for resolving a collision between a sphere and a non-axis aligned box. It relies on a couple of my own library routines, but it may prove useful to some. I'm using it in a game and it works perfectly.
float physicsProcessCollisionBetweenSelfAndActorRect(SPhysics *self, SPhysics *actor)
{
float diff = 99999;
SVector relative_position_of_circle = getDifference2DBetweenVectors(&self->worldPosition, &actor->worldPosition);
rotateVector2DBy(&relative_position_of_circle, -actor->axis.angleZ); // This aligns the coord system so the rect becomes an AABB
float x_clamped_within_rectangle = relative_position_of_circle.x;
float y_clamped_within_rectangle = relative_position_of_circle.y;
LIMIT(x_clamped_within_rectangle, actor->physicsRect.l, actor->physicsRect.r);
LIMIT(y_clamped_within_rectangle, actor->physicsRect.b, actor->physicsRect.t);
// Calculate the distance between the circle's center and this closest point
float distance_to_nearest_edge_x = relative_position_of_circle.x - x_clamped_within_rectangle;
float distance_to_nearest_edge_y = relative_position_of_circle.y - y_clamped_within_rectangle;
// If the distance is less than the circle's radius, an intersection occurs
float distance_sq_x = SQUARE(distance_to_nearest_edge_x);
float distance_sq_y = SQUARE(distance_to_nearest_edge_y);
float radius_sq = SQUARE(self->physicsRadius);
if(distance_sq_x + distance_sq_y < radius_sq)
{
float half_rect_w = (actor->physicsRect.r - actor->physicsRect.l) * 0.5f;
float half_rect_h = (actor->physicsRect.t - actor->physicsRect.b) * 0.5f;
CREATE_VECTOR(push_vector);
// If we're at one of the corners of this object, treat this as a circular/circular collision
if(fabs(relative_position_of_circle.x) > half_rect_w && fabs(relative_position_of_circle.y) > half_rect_h)
{
SVector edges;
if(relative_position_of_circle.x > 0) edges.x = half_rect_w; else edges.x = -half_rect_w;
if(relative_position_of_circle.y > 0) edges.y = half_rect_h; else edges.y = -half_rect_h;
push_vector = relative_position_of_circle;
moveVectorByInverseVector2D(&push_vector, &edges);
// We now have the vector from the corner of the rect to the point.
float delta_length = getVector2DMagnitude(&push_vector);
float diff = self->physicsRadius - delta_length; // Find out how far away we are from our ideal distance
// Normalise the vector
push_vector.x /= delta_length;
push_vector.y /= delta_length;
scaleVector2DBy(&push_vector, diff); // Now multiply it by the difference
push_vector.z = 0;
}
else // Nope - just bouncing against one of the edges
{
if(relative_position_of_circle.x > 0) // Ball is to the right
push_vector.x = (half_rect_w + self->physicsRadius) - relative_position_of_circle.x;
else
push_vector.x = -((half_rect_w + self->physicsRadius) + relative_position_of_circle.x);
if(relative_position_of_circle.y > 0) // Ball is above
push_vector.y = (half_rect_h + self->physicsRadius) - relative_position_of_circle.y;
else
push_vector.y = -((half_rect_h + self->physicsRadius) + relative_position_of_circle.y);
if(fabs(push_vector.x) < fabs(push_vector.y))
push_vector.y = 0;
else
push_vector.x = 0;
}
diff = 0; // Cheat, since we don't do anything with the value anyway
rotateVector2DBy(&push_vector, actor->axis.angleZ);
SVector *from = &self->worldPosition;
moveVectorBy2D(from, push_vector.x, push_vector.y);
}
return diff;
}
If you are interested in a more graphical solution which even works on (in plane) rotated rectangles..
Demo: https://jsfiddle.net/exodus4d/94mxLvqh/2691/
The idea is:
Translate the scenary to the origin [0,0]
In case the rect is not in plane, the rotation center should be at
[0, 0]
Rotate the scenary back into plane
Calculate intersection
const hasIntersection = ({x: cx, y: cy, r: cr}, {x, y, width, height}) => {
const distX = Math.abs(cx - x - width / 2);
const distY = Math.abs(cy - y - height / 2);
if (distX > (width / 2 + cr)) {
return false;
}
if (distY > (height / 2 + cr)) {
return false;
}
if (distX <= (width / 2)) {
return true;
}
if (distY <= (height / 2)) {
return true;
}
const Δx = distX - width / 2;
const Δy = distY - height / 2;
return Δx * Δx + Δy * Δy <= cr * cr;
};
const rect = new DOMRect(50, 20, 100, 50);
const circ1 = new DOMPoint(160, 80);
circ1.r = 20;
const circ2 = new DOMPoint(80, 95);
circ2.r = 20;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
ctx.beginPath();
ctx.strokeStyle = hasIntersection(circ1, rect) ? 'red' : 'green';
ctx.arc(circ1.x, circ1.y, circ1.r, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = hasIntersection(circ2, rect) ? 'red' : 'green';
ctx.arc(circ2.x, circ2.y, circ2.r, 0, 2 * Math.PI);
ctx.stroke();
<canvas id="canvas"></canvas>
Tip: Instead of rotating the rect (4 points). You can rotate the circle (1 point) in opposite direction.
To visualise, take your keyboard's numpad. If the key '5' represents your rectangle, then all the keys 1-9 represent the 9 quadrants of space divided by the lines that make up your rectangle (with 5 being the inside.)
1) If the circle's center is in quadrant 5 (i.e. inside the rectangle) then the two shapes intersect.
With that out of the way, there are two possible cases:
a) The circle intersects with two or more neighboring edges of the rectangle.
b) The circle intersects with one edge of the rectangle.
The first case is simple. If the circle intersects with two neighboring edges of the rectangle, it must contain the corner connecting those two edges. (That, or its center lies in quadrant 5, which we have already covered. Also note that the case where the circle intersects with only two opposing edges of the rectangle is covered as well.)
2) If any of the corners A, B, C, D of the rectangle lie inside the circle, then the two shapes intersect.
The second case is trickier. We should make note of that it may only happen when the circle's center lies in one of the quadrants 2, 4, 6 or 8. (In fact, if the center is on any of the quadrants 1, 3, 7, 8, the corresponding corner will be the closest point to it.)
Now we have the case that the circle's center is in one of the 'edge' quadrants, and it only intersects with the corresponding edge. Then, the point on the edge that is closest to the circle's center, must lie inside the circle.
3) For each line AB, BC, CD, DA, construct perpendicular lines p(AB,P), p(BC,P), p(CD,P), p(DA,P) through the circle's center P. For each perpendicular line, if the intersection with the original edge lies inside the circle, then the two shapes intersect.
There is a shortcut for this last step. If the circle's center is in quadrant 8 and the edge AB is the top edge, the point of intersection will have the y-coordinate of A and B, and the x-coordinate of center P.
You can construct the four line intersections and check if they lie on their corresponding edges, or find out which quadrant P is in and check the corresponding intersection. Both should simplify to the same boolean equation. Be wary of that the step 2 above did not rule out P being in one of the 'corner' quadrants; it just looked for an intersection.
Edit: As it turns out, I have overlooked the simple fact that #2 is a subcase of #3 above. After all, corners too are points on the edges. See #ShreevatsaR's answer below for a great explanation. And in the meanwhile, forget #2 above unless you want a quick but redundant check.
This function detect collisions (intersections) between Circle and Rectangle. He works like e.James method in his answer, but this one detect collisions for all angles of rectangle (not only right up corner).
NOTE:
aRect.origin.x and aRect.origin.y are coordinates of bottom left angle of rectangle!
aCircle.x and aCircle.y are coordinates of Circle Center!
static inline BOOL RectIntersectsCircle(CGRect aRect, Circle aCircle) {
float testX = aCircle.x;
float testY = aCircle.y;
if (testX < aRect.origin.x)
testX = aRect.origin.x;
if (testX > (aRect.origin.x + aRect.size.width))
testX = (aRect.origin.x + aRect.size.width);
if (testY < aRect.origin.y)
testY = aRect.origin.y;
if (testY > (aRect.origin.y + aRect.size.height))
testY = (aRect.origin.y + aRect.size.height);
return ((aCircle.x - testX) * (aCircle.x - testX) + (aCircle.y - testY) * (aCircle.y - testY)) < aCircle.radius * aCircle.radius;
}
Improving a little bit the answer of e.James:
double dx = abs(circle.x - rect.x) - rect.w / 2,
dy = abs(circle.y - rect.y) - rect.h / 2;
if (dx > circle.r || dy > circle.r) { return false; }
if (dx <= 0 || dy <= 0) { return true; }
return (dx * dx + dy * dy <= circle.r * circle.r);
This subtracts rect.w / 2 and rect.h / 2 once instead of up to three times.
I've a method which avoids the expensive pythagoras if not necessary - ie. when bounding boxes of the rectangle and the circle do not intersect.
And it'll work for non-euclidean too:
class Circle {
// create the bounding box of the circle only once
BBox bbox;
public boolean intersect(BBox b) {
// test top intersect
if (lat > b.maxLat) {
if (lon < b.minLon)
return normDist(b.maxLat, b.minLon) <= normedDist;
if (lon > b.maxLon)
return normDist(b.maxLat, b.maxLon) <= normedDist;
return b.maxLat - bbox.minLat > 0;
}
// test bottom intersect
if (lat < b.minLat) {
if (lon < b.minLon)
return normDist(b.minLat, b.minLon) <= normedDist;
if (lon > b.maxLon)
return normDist(b.minLat, b.maxLon) <= normedDist;
return bbox.maxLat - b.minLat > 0;
}
// test middle intersect
if (lon < b.minLon)
return bbox.maxLon - b.minLon > 0;
if (lon > b.maxLon)
return b.maxLon - bbox.minLon > 0;
return true;
}
}
minLat,maxLat can be replaced with minY,maxY and the same for minLon, maxLon: replace it with minX, maxX
normDist ist just a bit faster method then the full distance calculation. E.g. without the square-root in euclidean space (or without a lot of other stuff for haversine): dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Of course if you use that normDist method you'll need to do create a normedDist = dist*dist; for the circle
See the full BBox and Circle code of my GraphHopper project.
I created class for work with shapes
hope you enjoy
public class Geomethry {
public static boolean intersectionCircleAndRectangle(int circleX, int circleY, int circleR, int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight){
boolean result = false;
float rectHalfWidth = rectangleWidth/2.0f;
float rectHalfHeight = rectangleHeight/2.0f;
float rectCenterX = rectangleX + rectHalfWidth;
float rectCenterY = rectangleY + rectHalfHeight;
float deltax = Math.abs(rectCenterX - circleX);
float deltay = Math.abs(rectCenterY - circleY);
float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;
do{
// check that distance between the centerse is more than the distance between the circumcircle of rectangle and circle
if(lengthHypotenuseSqure > ((rectHalfWidth+circleR)*(rectHalfWidth+circleR) + (rectHalfHeight+circleR)*(rectHalfHeight+circleR))){
//System.out.println("distance between the centerse is more than the distance between the circumcircle of rectangle and circle");
break;
}
// check that distance between the centerse is less than the distance between the inscribed circle
float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
if(lengthHypotenuseSqure < ((rectMinHalfSide+circleR)*(rectMinHalfSide+circleR))){
//System.out.println("distance between the centerse is less than the distance between the inscribed circle");
result=true;
break;
}
// check that the squares relate to angles
if((deltax > (rectHalfWidth+circleR)*0.9) && (deltay > (rectHalfHeight+circleR)*0.9)){
//System.out.println("squares relate to angles");
result=true;
}
}while(false);
return result;
}
public static boolean intersectionRectangleAndRectangle(int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight, int rectangleX2, int rectangleY2, int rectangleWidth2, int rectangleHeight2){
boolean result = false;
float rectHalfWidth = rectangleWidth/2.0f;
float rectHalfHeight = rectangleHeight/2.0f;
float rectHalfWidth2 = rectangleWidth2/2.0f;
float rectHalfHeight2 = rectangleHeight2/2.0f;
float deltax = Math.abs((rectangleX + rectHalfWidth) - (rectangleX2 + rectHalfWidth2));
float deltay = Math.abs((rectangleY + rectHalfHeight) - (rectangleY2 + rectHalfHeight2));
float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;
do{
// check that distance between the centerse is more than the distance between the circumcircle
if(lengthHypotenuseSqure > ((rectHalfWidth+rectHalfWidth2)*(rectHalfWidth+rectHalfWidth2) + (rectHalfHeight+rectHalfHeight2)*(rectHalfHeight+rectHalfHeight2))){
//System.out.println("distance between the centerse is more than the distance between the circumcircle");
break;
}
// check that distance between the centerse is less than the distance between the inscribed circle
float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
float rectMinHalfSide2 = Math.min(rectHalfWidth2, rectHalfHeight2);
if(lengthHypotenuseSqure < ((rectMinHalfSide+rectMinHalfSide2)*(rectMinHalfSide+rectMinHalfSide2))){
//System.out.println("distance between the centerse is less than the distance between the inscribed circle");
result=true;
break;
}
// check that the squares relate to angles
if((deltax > (rectHalfWidth+rectHalfWidth2)*0.9) && (deltay > (rectHalfHeight+rectHalfHeight2)*0.9)){
//System.out.println("squares relate to angles");
result=true;
}
}while(false);
return result;
}
}
Here is the modfied code 100% working:
public static bool IsIntersected(PointF circle, float radius, RectangleF rectangle)
{
var rectangleCenter = new PointF((rectangle.X + rectangle.Width / 2),
(rectangle.Y + rectangle.Height / 2));
var w = rectangle.Width / 2;
var h = rectangle.Height / 2;
var dx = Math.Abs(circle.X - rectangleCenter.X);
var dy = Math.Abs(circle.Y - rectangleCenter.Y);
if (dx > (radius + w) || dy > (radius + h)) return false;
var circleDistance = new PointF
{
X = Math.Abs(circle.X - rectangle.X - w),
Y = Math.Abs(circle.Y - rectangle.Y - h)
};
if (circleDistance.X <= (w))
{
return true;
}
if (circleDistance.Y <= (h))
{
return true;
}
var cornerDistanceSq = Math.Pow(circleDistance.X - w, 2) +
Math.Pow(circleDistance.Y - h, 2);
return (cornerDistanceSq <= (Math.Pow(radius, 2)));
}
Bassam Alugili
Here's a fast one-line test for this:
if (length(max(abs(center - rect_mid) - rect_halves, 0)) <= radius ) {
// They intersect.
}
This is the axis-aligned case where rect_halves is a positive vector pointing from the rectangle middle to a corner. The expression inside length() is a delta vector from center to a closest point in the rectangle. This works in any dimension.
First check if the rectangle and the square tangent to the circle overlaps (easy). If they do not overlaps, they do not collide.
Check if the circle's center is inside the rectangle (easy). If it's inside, they collide.
Calculate the minimum squared distance from the rectangle sides to the circle's center (little hard). If it's lower that the squared radius, then they collide, else they don't.
It's efficient, because:
First it checks the most common scenario with a cheap algorithm and when it's sure they do not collide, it ends.
Then it checks the next most common scenario with a cheap algorithm (do not calculate square root, use the squared values) and when it's sure they collide it ends.
Then it executes the more expensive algorithm to check collision with the rectangle borders.
worked for me (only work when angle of rectangle is 180)
function intersects(circle, rect) {
let left = rect.x + rect.width > circle.x - circle.radius;
let right = rect.x < circle.x + circle.radius;
let top = rect.y < circle.y + circle.radius;
let bottom = rect.y + rect.height > circle.y - circle.radius;
return left && right && bottom && top;
}
For those have to calculate Circle/Rectangle collision in Geographic Coordinates with SQL,
this is my implementation in oracle 11 of e.James suggested algorithm.
In input it requires circle coordinates, circle radius in km and two vertices coordinates of the rectangle:
CREATE OR REPLACE FUNCTION "DETECT_CIRC_RECT_COLLISION"
(
circleCenterLat IN NUMBER, -- circle Center Latitude
circleCenterLon IN NUMBER, -- circle Center Longitude
circleRadius IN NUMBER, -- circle Radius in KM
rectSWLat IN NUMBER, -- rectangle South West Latitude
rectSWLon IN NUMBER, -- rectangle South West Longitude
rectNELat IN NUMBER, -- rectangle North Est Latitude
rectNELon IN NUMBER -- rectangle North Est Longitude
)
RETURN NUMBER
AS
-- converts km to degrees (use 69 if miles)
kmToDegreeConst NUMBER := 111.045;
-- Remaining rectangle vertices
rectNWLat NUMBER;
rectNWLon NUMBER;
rectSELat NUMBER;
rectSELon NUMBER;
rectHeight NUMBER;
rectWIdth NUMBER;
circleDistanceLat NUMBER;
circleDistanceLon NUMBER;
cornerDistanceSQ NUMBER;
BEGIN
-- Initialization of remaining rectangle vertices
rectNWLat := rectNELat;
rectNWLon := rectSWLon;
rectSELat := rectSWLat;
rectSELon := rectNELon;
-- Rectangle sides length calculation
rectHeight := calc_distance(rectSWLat, rectSWLon, rectNWLat, rectNWLon);
rectWidth := calc_distance(rectSWLat, rectSWLon, rectSELat, rectSELon);
circleDistanceLat := abs( (circleCenterLat * kmToDegreeConst) - ((rectSWLat * kmToDegreeConst) + (rectHeight/2)) );
circleDistanceLon := abs( (circleCenterLon * kmToDegreeConst) - ((rectSWLon * kmToDegreeConst) + (rectWidth/2)) );
IF circleDistanceLon > ((rectWidth/2) + circleRadius) THEN
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLat > ((rectHeight/2) + circleRadius) THEN
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLon <= (rectWidth/2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLat <= (rectHeight/2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
cornerDistanceSQ := POWER(circleDistanceLon - (rectWidth/2), 2) + POWER(circleDistanceLat - (rectHeight/2), 2);
IF cornerDistanceSQ <= POWER(circleRadius, 2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
ELSE
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END;
Works, just figured this out a week ago, and just now got to testing it.
double theta = Math.atan2(cir.getX()-sqr.getX()*1.0,
cir.getY()-sqr.getY()*1.0); //radians of the angle
double dBox; //distance from box to edge of box in direction of the circle
if((theta > Math.PI/4 && theta < 3*Math.PI / 4) ||
(theta < -Math.PI/4 && theta > -3*Math.PI / 4)) {
dBox = sqr.getS() / (2*Math.sin(theta));
} else {
dBox = sqr.getS() / (2*Math.cos(theta));
}
boolean touching = (Math.abs(dBox) >=
Math.sqrt(Math.pow(sqr.getX()-cir.getX(), 2) +
Math.pow(sqr.getY()-cir.getY(), 2)));
def colision(rect, circle):
dx = rect.x - circle.x
dy = rect.y - circle.y
distance = (dy**2 + dx**2)**0.5
angle_to = (rect.angle + math.atan2(dx, dy)/3.1415*180.0) % 360
if((angle_to>135 and angle_to<225) or (angle_to>0 and angle_to<45) or (angle_to>315 and angle_to<360)):
if distance <= circle.rad/2.+((rect.height/2.0)*(1.+0.5*abs(math.sin(angle_to*math.pi/180.)))):
return True
else:
if distance <= circle.rad/2.+((rect.width/2.0)*(1.+0.5*abs(math.cos(angle_to*math.pi/180.)))):
return True
return False
I developed this algorithm while making this game: https://mshwf.github.io/mates/
If the circle touches the square, then the distance between the centerline of the circle and the centerline of the square should equal (diameter+side)/2.
So, let's have a variable named touching that holds that distance. The problem was: which centerline should I consider: the horizontal or the vertical?
Consider this frame:
Each centerline gives different distances, and only one is a correct indication to a no-collision, but using our human intuition is a start to understand how the natural algorithm works.
They are not touching, which means that the distance between the two centerlines should be greater than touching, which means that the natural algorithm picks the horizontal centerlines (the vertical centerlines says there's a collision!). By noticing multiple circles, you can tell: if the circle intersects with the vertical extension of the square, then we pick the vertical distance (between the horizontal centerlines), and if the circle intersects with the horizontal extension, we pick the horizontal distance:
Another example, circle number 4: it intersects with the horizontal extension of the square, then we consider the horizontal distance which is equal to touching.
Ok, the tough part is demystified, now we know how the algorithm will work, but how we know with which extension the circle intersects?
It's easy actually: we calculate the distance between the most right x and the most left x (of both the circle and the square), and the same for the y-axis, the one with greater value is the axis with the extension that intersects with the circle (if it's greater than diameter+side then the circle is outside the two square extensions, like circle #7). The code looks like:
right = Math.max(square.x+square.side, circle.x+circle.rad);
left = Math.min(square.x, circle.x-circle.rad);
bottom = Math.max(square.y+square.side, circle.y+circle.rad);
top = Math.min(square.y, circle.y-circle.rad);
if (right - left > down - top) {
//compare with horizontal distance
}
else {
//compare with vertical distance
}
/*These equations assume that the reference point of the square is at its top left corner, and the reference point of the circle is at its center*/
do a pre-check whether a circle fully encapsulating the rectangle collides with the circle.
check for rectangle corners within the circle.
For each edge, see if there is a line intersection with the circle. Project the center point C onto the line AB to get a point D. If the length of CD is less than radius, there was a collision.
projectionScalar=dot(AC,AB)/(mag(AC)*mag(AB));
if(projectionScalar>=0 && projectionScalar<=1) {
D=A+AB*projectionScalar;
CD=D-C;
if(mag(CD)<circle.radius){
// there was a collision
}
}
There is an incredibly simple way to do this, you have to clamp a point in x and y, but inside the square, while the center of the circle is between the two square border points in one of the perpendicular axis you need to clamp those coordinates to the parallel axis, just make sure the clamped coordinates do not exeed the limits of the square.
Then just get the distance between the center of the circle and the clamped coordinates and check if the distance is less than the radius of the circle.
Here is how I did it (First 4 points are the square coordinates, the rest are circle points):
bool DoesCircleImpactBox(float x, float y, float x1, float y1, float xc, float yc, float radius){
float ClampedX=0;
float ClampedY=0;
if(xc>=x and xc<=x1){
ClampedX=xc;
}
if(yc>=y and yc<=y1){
ClampedY=yc;
}
radius = radius+1;
if(xc<x) ClampedX=x;
if(xc>x1) ClampedX=x1-1;
if(yc<y) ClampedY=y;
if(yc>y1) ClampedY=y1-1;
float XDif=ClampedX-xc;
XDif=XDif*XDif;
float YDif=ClampedY-yc;
YDif=YDif*YDif;
if(XDif+YDif<=radius*radius) return true;
return false;
}
My method:
Calculate closest_point from the circle on/in OBB / rectangle
(Closest point will lie on an edge/corner or inside)
Calculate squared_distance from the closest_point to the centre of the circle
(Squared distance avoids a square root)
Return squared_distance <= circle radius squared
Assuming you have the four edges of the rectangle check the distance from the edges to the center of the circle, if its less then the radius, then the shapes are intersecting.
if sqrt((rectangleRight.x - circleCenter.x)^2 +
(rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleRight.x - circleCenter.x)^2 +
(rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleLeft.x - circleCenter.x)^2 +
(rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleLeft.x - circleCenter.x)^2 +
(rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect

Resources