How to define the intersection of three circles? - javascript

Given three circles with their center point and radius, how can you define the area of intersection?
So far what I have is:
var point1 = {x: -3, y: 0};
var point2 = {x: 3, y: 0};
var point3 = {x: 0, y: -3};
var r1 = 5;
var r2 = 5;
var r3 = 5;
var area = returnIntersectionArea(point1, point2, point3, r1, r2, r3);
Also, if two collide but not the third, the function should return null.
If none collide, null should be returned.

This article describes how to find the area of the intersection between two circles. The result it easily extended to three circles.
-------------EDIT-------------
OK, the problem is not easily extended to three circles, I found PhD theses on the subject. Assuming the three circles intersect as shown below, an approximate solution can be found (I think). Before we attempt it, we must check if the three circles indeed intersect as shown below. The problem changes quite a bit if say one circle is inside the other and the third intersects them both.
.
Let S1,S2 and S3 denote the areas of the three circles, and X1,X2 and X3 denote the area of the intersections between each pair of circles (index increases in clockwise direction). As we already established, there are exact formulae for these. Consider the following system of linear equations:
A+D+F+G = A+D+X1 = S1
B+D+E+G = B+D+ X3 = S2
B+E+D+G = B+E+X2 = S3
It is underdetermined, but an approximate solution can be found using least squares. I haven't tried it numerically but will get back to you as soon as I do :D
If the least-squares solution seems wrong, we should also impose several constraints, e.g. the area if the intersection between any pair of circles is smaller than the area of the circles.
Comments are appreciated.
PS +1 to Simon for pointing out I shouldn't qualify things as easy

One way of approaching this problem is via a Monte Carlo simulation:
function returnIntersectionArea(point1, point2, point3, r1, r2, r3) {
// determine bounding rectangle
var left = Math.min(point1.x - r1, point2.x - r2, point3.x - r3);
var right = Math.max(point1.x + r1, point2.x + r2, point3.x + r3);
var top = Math.min(point1.y - r1, point2.y - r2, point3.y - r3);
var bottom = Math.max(point1.y + r1, point2.y + r2, point3.y + r3);
// area of bounding rectangle
var rectArea = (right - left) * (bottom - top);
var iterations = 10000;
var pts = 0;
for (int i=0; i<iterations; i++) {
// random point coordinates
var x = left + Math.rand() * (right - left);
var y = top + Math.rand() * (bottom - top);
// check if it is inside all the three circles (the intersecting area)
if (Math.sqrt(Math.pow(x - point1.x, 2) + Math.pow(y - point1.y, 2)) <= r1 &&
Math.sqrt(Math.pow(x - point2.x, 2) + Math.pow(y - point2.y, 2)) <= r2 &&
Math.sqrt(Math.pow(x - point3.x, 2) + Math.pow(y - point3.y, 2)) <= r3)
pts++;
}
// the ratio of points inside the intersecting area will converge to the ratio
// of the area of the bounding rectangle and the intersection
return pts / iterations * rectArea;
}
The solution can be improved to arbitrary precision (within floating-point limits) by increasing the number of iterations, although the rate at which the solution is approached may become slow. Obviously, choosing a tight bounding box is important for achieving good convergence.

Related

How to get a better approximation of a thick bezier curve?

Let's say I already have a bezier curve approximated by many straight lines (the bezier array in the code), and I would like to draw it with a series of rectangles. I have the following code below that does exactly this:
// don't change this array
const bezier = [{x:167.00,y:40.00},{x:154.37,y:42.09},{x:143.09,y:44.48},{x:133.08,y:47.15},{x:124.26,y:50.09},{x:116.55,y:53.27},{x:109.87,y:56.68},{x:104.15,y:60.31},{x:99.32,y:64.14},{x:95.28,y:68.15},{x:91.97,y:72.34},{x:89.31,y:76.67},{x:87.22,y:81.14},{x:85.63,y:85.74},{x:84.44,y:90.43},{x:83.60,y:95.22},{x:83.02,y:100.08},{x:82.63,y:105.00},{x:82.33,y:109.96},{x:82.07,y:114.94},{x:81.76,y:119.94},{x:81.33,y:124.93},{x:80.69,y:129.89},{x:79.77,y:134.82},{x:78.49,y:139.70},{x:76.78,y:144.50},{x:74.55,y:149.22},{x:71.74,y:153.84},{x:68.25,y:158.34},{x:64.03,y:162.71},{x:58.97,y:166.93},{x:53.02,y:170.98},{x:46.10,y:174.86},{x:38.11,y:178.54},{x:29.00,y:182.00}];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const thickness = 35;
function rotateCanvas(x, y, a) {
ctx.translate(x, y);
ctx.rotate(a);
ctx.translate(-x, -y);
}
function drawRectangle(rX, rY, rW, rH, rA, color) {
ctx.beginPath();
rotateCanvas(rX + rW / 2, rY + rH / 2, rA);
ctx.rect(rX, rY, rW, rH);
rotateCanvas(rX + rW / 2, rY + rH / 2, -rA);
ctx.fill();
}
function calcRectFromLine(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const mag = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
return {
x: (x1 + x2) / 2 - mag / 2,
y: (y1 + y2) / 2 - thickness / 2,
w: mag,
h: thickness,
a: angle
};
}
function calculateRectangles() {
const result = [];
for (let i = 1; i < bezier.length; i++) {
const prev = bezier[i - 1];
const curr = bezier[i];
result.push(calcRectFromLine(prev.x, prev.y, curr.x, curr.y));
}
return result;
}
const rectangles = calculateRectangles();
for (let r of rectangles) {
drawRectangle(r.x, r.y, r.w, r.h, r.a);
}
<canvas width="400" height="400"></canvas>
If you run the snippet you'll see that the curve is not fully thick, and the fact that it is a series of rectangles is very obvious.
If you change the thickness parameter from 35 to a lower number and re-run it, it looks fine. It's only when it's very thick does this occur.
The code currently takes the bezier array, and creates a series of rotated rectangles and then renders them.
Is there any way to modify the calculateRectangles function to return a better approximation of the curve? Ideally it would still return a list of rectangles rotated around their center, but when rendered it would look more like the curve, and less like a list of rectangles.
The only idea I could think of is to somehow return twice as many rectangles from calculateRectangles, where each one is inverted from the previous one, such that both sides of the line are filled in, and while I think that might work, it unfortunately has the side-effect of returning twice as many rectangles, which is undesirable and I would to avoid it if possible.
The shapes that you should draw are not rectangles but quadrilaterals, obtained by joining the endpoints of the successive normals to the curve. Presumably, you can achieve that by means of Path objects.
In zones of high curvature, you may have to yet reduce the step, because the outer curve might not be smooth.
In fact, you can "flatten" a Bezier curve by choosing steps so that the deviation between successive segments remains bounded below a fixed tolerance.
In the case of a thick curve, you can keep that idea but making sure that the bounded deviation holds for both sides of the curve.
you can't really make a "thick bezier" by drawing rectangles, you're just going to end up with lots of gaps between them on one size, and weird looking overlap on the other side. If you want to stick with the polygon approximation, you'll need to use the normal at each of your points, and then draw lines to connect those. This leads to trapezoidal sections, so we can't use plain rects if we want decent looking results.
However, the bigger the offset, the more you're going to have problems in areas with tiny radius of curvature: you can already see one such problem just by looking at the normals crossing each other underneath the crest in the upper left, where we don't actually want to connect all normal-offset vertices, because one of them lies inside the shape we want to trace.
Alternatively, you can offset the bezier curve itself with "more beziers", e.g. https://pomax.github.io/bezierinfo/#offsetting, but even then you're still going to have to resolve overlaps in the offset shape.
Instead, you can approximate the curve using circular arcs, which make "thickening the curve" a bit easier because you simply use the same arc angles and center, with two different values for the radius to get your two offset segments.
This is an okay first attempt, but I'm going to keep trying. Simply add this to the end of the getRectangles function add further approximation rectangles. Seems good enough for my purposes (and simple!), but I'm going to keep investigating a bit. I'm aware it doesn't work perfectly, but it's okay, and I don't really need much better than okay:
let len = result.length;
for (let i = 1; i < len; i++) {
const prevR = result[i - 1];
const currR = result[i - 0];
result.push({
x: (prevR.x + currR.x) / 2,
y: (prevR.y + currR.y) / 2,
w: (prevR.w + currR.w) / 2,
h: (prevR.h + currR.h) / 2,
a: (prevR.a + currR.a) / 2
});
}
Actually, this is slightly better than okay the more and more I play with it. I think this might be a good enough solution. Unless someone can come up with something better.
Here's a GIF of the difference:

Generate Radial/Ellipse/Oval/Stadium Gradient Mask (Nested For Loop)

What I'm attempting to do
Loop through two axes and generating a shape with a width and height, either less or equal to the length of the nested for-loops, and calculate the distance from all positions to the center of that shape.
Main Issue(s)
How do I specify the width and height of an ellipse shape to draw using a nested for-loop with different dimensions to that ellipse?
For example a nested for-loop which goes for 0 to 45 in the X axis, and 0 to 100 in the Y axis but draws an ellipse with a width of 39 and a height of 90 - with the remaining difference used as padding (3 on either side, and 5 on top and bottom).
I have this half working using the EdgeOrInBounds function below, however I'm having trouble understanding why the values I'm using are giving the results they are.
Using a nested for-loop the same as above, but specifying an ellipse with a width of 30 and a height of 70 doesn't have the expected padding, it instead draws an ellipse with only one extra sprite surrounding all sides.
How do I calculate the distance from the center of the ellipse to the positions generated by the nested for-loop as a value between zero and one?
For example, any position outside the ellipse returns a value of zero and any position within the ellipse returns the distance scaled between zero and one from the center of the ellipse.
Similar to above, I have this half working as I can return a value of zero for all posiitons outside of the ellipse, but I do not understand how scale the distances for positions within the ellipse.
Bonus Issue(s)
I'm doing this on a platform where code isn't easily shareable and there are few built in functions, so I've had to create my own versions stolen from based on examples from the Nvidia developer site.
I have a basic understanding of some C# and JavaScript, but zero understanding of mathematical formulas.
Ellipse Function(s)
bool EdgeOrInBounds (Vector2 position) {
int x = ((int) Math.Pow (position.x - center.x, 2) / (int) Math.Pow (radius.x, 2));
int y = ((int) Math.Pow (position.y - center.y, 2) / (int) Math.Pow (radius.y, 2));
return (x + y <= 1);
}
Distance Function(s)
float distance (Vector2 position) {
return (sqrt (dot (centerPosition - position, centerPosition - position));
}
float dot (Vector2 a, Vector2 b) {
return (a.x * b.x + a.y * b.y);
}
float sqrt (float a) {
return (1.0 / pow (a, -0.5));
}
Variables
int mapWidth = 45;
int mapHeight = 100;
Vector2 radius = new Vector2 (mapWidth - 8, mapHeight - 4);
Vector2 center = new Vector2 (mapWidth / 2, mapHeight / 2);
Nested For Loops
for (int x = 0; x < width; x ++) {
for (int y = 0; y < height; y ++) {
// Store current position to reference in a minute
Vector2 position = new Vector2 (x, y);
// Check if position is within bounds or lies on the edge of the ellipse
if (EdgeOrInBounds (position)) {
// Calculate distance from center to current position
float dist = distance (position);
}
}
}
Example Image:
Closing Remarks
I know I haven't done a good job of explaining what I'm tring to achieve, so I'd like to apologize in advance, and I'd also like to thank anyone who reads this as any help would be very much appreciated.
Cheers.
To get color shade better under control, you could use an elliptic spiral, instead of a square grid traverse. Start out with the two radii, use X=R1 * Cos(angle) and Y=R2 * Sin(angle), where you gradually decrease R1 and R2 to zero. Your loop will use polar coordinates (angle,r), see below. You are then sure of the size of your "plot" and you won't need to test distances underways. It can probably do without any distance function for color scaling, but I'm not sure how to do that properly.. I have included a few options.
// The image is 440x240, I want ellipse in the center, margins 20 pix
// Parameters, dependent on size and shape of elllipse
Point pc = new Point(220,120); // pixel center
double r1=200; // radius 1 margin 2x20 on 440
double r2=100; // radius 2 margin 2x20 on 240
// Covering all pixels
int rmax = (int)Math.Max(r1,r2);
// scaling for color
var ravgmax = (r1+r2)/2.0;
// Find suitable loop counts
var nr = rmax; // number of radius steps in loop
var nh = 2*nr*Math.PI); // number of angles in loop
// Prepare initial loop displacements
var h=0.0;
var dr1 = r1/(nr*nh);
var dr2 = r2/(nr*nh);
var dh=(Math.PI*2.0)/nh;
// The loop
for (int i=0; i<nr; i++)
{
for (int j=0; j<(int)nh; j++)
{
var p = new PointF((float)(pc.X+r1*Math.Cos(h)),(float)(pc.Y+r2*Math.Sin(h)));
// vanilla shading
// int grayscale = 255 - (int)(255 * ((r1+r2)/2.0)/ravgmax );
// elliptical option without using distance, scale along axes
// grayscale = 255 - (int)(Math.Abs(p.X-pc.X)*255/200+Math.Abs((p.Y-pc.Y)*255/100)/2;
// "Distance grayscale" which is circular, not elliptical
int grayscale = (int)(255 * floatFDistance(p,pc)/rmax);
PlotF(p,grayscale); // you provide: plotpixel(PointF, int)
r1-=dr1; r2-=dr2;
h+=dh;
}
}
}
float floatFDistance(PointF p1, PointF p2)
{
double d1 = (p1.X - p2.X);
double d2 = (p1.Y - p2.Y);
return (float)(Math.Sqrt(d1 * d1 + d2 * d2));
}

For the point inside circle, find in which quarter it is?

I researched google but couldn't find the keywords for search. So I ask here if my algorithm and code is efficient?
http://sketchtoy.com/66429941 (algorithm)
The algoritm is: I have four points which are: north, east, south and west of circle. I check 4 distances (distanceToNorth, distanceToEast, distanceToSouth, distanceToWest). And I find minimum of them so that is the quarter.
Here is the code but it does not seem efficient for me.
(firstQuarter is North, secondQuarter is East and so on..
note: assume that mousemove is inside the circle.
var firstQuarterX = centerX;
var firstQuarterY = centerY - radius;
var secondQuarterX = centerX + radius;
var secondQuarterY = centerY;
var thirdQuarterX = centerX;
var thirdQuarterY = centerY + radius;
var fourthQuarterX = centerX - radius;
var fourthQuarterY = centerY;
var distanceToFirst = Math.sqrt(Math.pow(x-firstQuarterX, 2) + Math.pow(y-firstQuarterY, 2));
var distanceToSecond = Math.sqrt(Math.pow(x-secondQuarterX, 2) + Math.pow(y-secondQuarterY, 2));
var distanceToThird = Math.sqrt(Math.pow(x-thirdQuarterX, 2) + Math.pow(y-thirdQuarterY, 2));
var distanceToFourth = Math.sqrt(Math.pow(x-fourthQuarterX, 2) + Math.pow(y-fourthQuarterY, 2));
var min = Math.min(distanceToFirst, distanceToSecond, distanceToThird, distanceToFourth);
var numbers = [distanceToFirst, distanceToSecond, distanceToThird, distanceToFourth];
var index = numbers.indexOf(min); // it will give 0 or 1 or 2 or 3
var quarter = index + 1;
Observe that the boundaries between your quarters lie along the lines with equations y = x and y = -x, relative to an origin at the center of the circle. You can use those to evaluate which quarter each point falls in.
If your point is (x, y), then its coordinates relative to the center of the circle are xRelative = x - centerX and yRelative = y - centerY. Then
your point is in the first (south in your code) quarter if yRelative < 0 and Math.abs(xRelative) < -yRelative
your point is in the second (east) quarter if xRelative > 0 and Math.abs(yRelative) < xRelative
your point is in the third (north) quarter if yRelative > 0 and Math.abs(xRelative) < yRelative
your point is in the fourth (west) quarter if xRelative < 0 and Math.abs(yRelative) < -xRelative
I leave it to you to determine to which quarter to assign points that fall exactly on a boundary. Also, you can implement a little decision tree based on those criteria if you prefer; that should be a little more efficient then testing each criterion in turn.
Not so sure but I think this might work. Math.atan2(CenterY - y, CenterX - x) * 180 / Math.PI gives the apparent angle between the points. Do the remaining math to figure out the quarter.
What about something like:
return x>centerX?(y>centerY?"Quad 2":"Quad 1"):(y>centerY?"Quad 3":"Quad 4");
Less graceful, more slim.
For more efficient algorithm, you can compute the quadrant just by analyzing the signs of dx + dy and dx - dy quantities (dx, dy being x, y minus centerX, centerY respectively) (I presume that as your animation shows, your quadrants are rotated by 45 degrees against 'standard' quadrants.

Finding point n% away from the centre of a semicircle in Javascript?

I'm sorry to say that Math really isn't my strong suit. Normally I can get by, but this has got me totally stumped.
I'm trying to code up a quiz results screen in HTML/CSS/Javascript.
On my interface, I have a semicircle (the right hemisphere of a target).
I have a range of 'scores' (integers out of 100 - so 50, 80, 90 etc.).
I need to plot these points on the semicircle to be n% away from the centre, where n is the value of each score - the higher the score, the closer to the centre of the target the point will appear.
I know how wide my semicircle is, and have already handled the conversion of the % values so that the higher ones appear closer to the centre while the lower ones appear further out.
What I can't wrap my head around is plotting these points on a line that travels out from the centre point (x = 0, y = target height/2) of the target at a random angle (so the points don't overlap).
Any suggestions are gratefully received!
Do you have an example of what you want this to look like? It sounds like you want to divide up the circle into N slices where N is the number of points you need to display, then plot the points along each of those radii. So you might have something like:
Edit: code was rotating about the origin, not the circle specified
var scores = [];
//...
//assume scores is an array of distances from the center of the circle
var points = [];
var interval = 2 * Math.PI / N;
var angle;
for (var i = 0; i < N; i++) {
angle = interval * i;
//assume (cx, cy) are the coordinates of the center of your circle
points.push({
x: scores[i] * Math.cos(angle) + cx,
y: scores[i] * Math.sin(angle) + cy
});
}
Then you can plot points however you see fit.
After much headscratching, I managed to arrive at this solution (with the help of a colleague who's much, much better at this kind of thing than me):
(arr_result is an array containing IDs and scores - scores are percentages of 100)
for (var i = 0; i < arr_result.length; i++){
var angle = angleArray[i]; // this is an array of angles (randomised) - points around the edge of the semicircle
var radius = 150; // width of the semicircle
var deadZone = 25 // to make matters complicated, the circle has a 'dead zone' in the centre which we want to discount
var maxScore = 100
var score = parseInt(arr_result[i]['score'], 10)
var alpha = angle * Math.PI
var distance = (maxScore-score)/maxScore*(radius-deadZone) + deadZone
var x = distance * Math.sin(alpha)
var y = radius + distance * Math.cos(alpha)
$('#marker_' + arr_result[i]['id'], templateCode).css({ // target a specific marker and move it using jQuery
'left' : pointX,
'top': pointY
});
}
I've omitted the code for generating the array of angles and randomising that array - that's only needed for presentational purposes so the markers don't overlap.
I also do some weird things with the co-ordinates before I move the markers (again, this has been omitted) as I want the point to be at the bottom-centre of the marker rather than the top-left.

Find column, row on 2D isometric grid from x,y screen space coords (Convert equation to function)

I'm trying to find the row, column in a 2d isometric grid of a screen space point (x, y)
Now I pretty much know what I need to do which is find the length of the vectors in red in the pictures above and then compare it to the length of the vector that represent the bounds of the grid (which is represented by the black vectors)
Now I asked for help over at mathematics stack exchange to get the equation for figuring out what the parallel vectors are of a point x,y compared to the black boundary vectors. Link here Length of Perpendicular/Parallel Vectors
but im having trouble converting this to a function
Ideally i need enough of a function to get the length of both red vectors from three sets of points, the x,y of the end of the 2 black vectors and the point at the end of the red vectors.
Any language is fine but ideally javascript
What you need is a base transformation:
Suppose the coordinates of the first black vector are (x1, x2) and the coordinates of the second vector are (y1, y2).
Therefore, finding the red vectors that get at a point (z1, z2) is equivalent to solving the following linear system:
x1*r1 + y1*r2 = z1
x2*r1 + y2*r2 = z2
or in matrix form:
A x = b
/x1 y1\ |r1| = |z1|
\x2 y2/ |r2| |z2|
x = inverse(A)*b
For example, lets have the black vector be (2, 1) and (2, -1). The corresponding matrix A will be
2 2
1 -1
and its inverse will be
1/4 1/2
1/4 -1/2
So a point (x, y) in the original coordinates will be able to be represened in the alternate base, bia the following formula:
(x, y) = (1/4 * x + 1/2 * y)*(2,1) + (1/4 * x -1/2 * y)*(2, -1)
What exactly is the point of doing it like this? Any isometric grid you display usually contains cells of equal size, so you can skip all the vector math and simply do something like:
var xStep = 50,
yStep = 30, // roughly matches your image
pointX = 2*xStep,
pointY = 0;
Basically the points on any isometric grid fall onto the intersections of a non-isometric grid. Isometric grid controller:
screenPositionToIsoXY : function(o, w, h){
var sX = ((((o.x - this.canvas.xPosition) - this.screenOffsetX) / this.unitWidth ) * 2) >> 0,
sY = ((((o.y - this.canvas.yPosition) - this.screenOffsetY) / this.unitHeight) * 2) >> 0,
isoX = ((sX + sY - this.cols) / 2) >> 0,
isoY = (((-1 + this.cols) - (sX - sY)) / 2) >> 0;
// isoX = ((sX + sY) / isoGrid.width) - 1
// isoY = ((-2 + isoGrid.width) - sX - sY) / 2
return $.extend(o, {
isoX : Math.constrain(isoX, 0, this.cols - (w||0)),
isoY : Math.constrain(isoY, 0, this.rows - (h||0))
});
},
// ...
isoToUnitGrid : function(isoX, isoY){
var offset = this.grid.offset(),
isoX = $.uD(isoX) ? this.isoX : isoX,
isoY = $.uD(isoY) ? this.isoY : isoY;
return {
x : (offset.x + (this.grid.unitWidth / 2) * (this.grid.rows - this.isoWidth + isoX - isoY)) >> 0,
y : (offset.y + (this.grid.unitHeight / 2) * (isoX + isoY)) >> 0
};
},
Okay so with the help of other answers (sorry guys neither quite provided the answer i was after)
I present my function for finding the grid position on an iso 2d grid using a world x,y coordinate where the world x,y is an offset screen space coord.
WorldPosToGridPos: function(iPosX, iPosY){
var d = (this.mcBoundaryVectors.upper.x * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.upper.y * this.mcBoundaryVectors.lower.x);
var a = ((iPosX * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.lower.x * iPosY)) / d;
var b = ((this.mcBoundaryVectors.upper.x * iPosY) - (iPosX * this.mcBoundaryVectors.upper.y)) / d;
var cParaUpperVec = new Vector2(a * this.mcBoundaryVectors.upper.x, a * this.mcBoundaryVectors.upper.y);
var cParaLowerVec = new Vector2(b * this.mcBoundaryVectors.lower.x, b * this.mcBoundaryVectors.lower.y);
var iGridWidth = 40;
var iGridHeight = 40;
var iGridX = Math.floor((cParaLowerVec.length() / this.mcBoundaryVectors.lower.length()) * iGridWidth);
var iGridY = Math.floor((cParaUpperVec.length() / this.mcBoundaryVectors.upper.length()) * iGridHeight);
return {gridX: iGridX, gridY: iGridY};
},
The first line is best done once in an init function or similar to save doing the same calculation over and over, I just included it for completeness.
The mcBoundaryVectors are two vectors defining the outer limits of the x and y axis of the isometric grid (The black vectors shown in the picture above).
Hope this helps anyone else in the future

Categories