How can I fix this code to properly detect overlapping circles?
The first circle is appointed by testing the location of the starting point. This first circle should be the basis of the overlapping circle map. Now, it only works if the tested circles overlap in a non-branching line...
(individual circles come as [x,y,radius])
var circles = [
[6, 19, 1],
[6, 11, 4],
[8, 17, 3],
[19, 19, 2],
[19, 11, 4],
[15, 7, 6],
[12, 19, 4]
];
var i = 0;
var j = 0;
var start = [10, 19];
var starts = false;
var overlapping = [];
var isInside = function(point, list, check, push) {
var temp = list.filter(function(item) { return Math.pow(item[0] - point[0], 2) + Math.pow(item[1] - point[1], 2) < item[2] * item[2] });
if (push) { overlapping = overlapping.concat(temp) };
return temp.length > 0
};
starts = isInside(start, circles, starts, true);
var overlappingCirclesTest = function() {
if (j < circles.length && overlapping.length > 0) {
var i = overlapping.length - 1;
var r0 = overlapping[i][2];
var r1 = circles[j][2];
var x0 = overlapping[i][0];
var x1 = circles[j][0];
var y0 = overlapping[i][1];
var y1 = circles[j][1];
if (Math.hypot(x0 - x1, y0 - y1) <= (r0 + r1)) {
overlapping.push(circles[j]);
circles.splice(circles.indexOf(circles[j]), 1);
j = 0;
overlappingCirclesTest();
}
j++;
overlappingCirclesTest();
}
}
overlappingCirclesTest();
EDIT: for clarification: we have an array of potentially overlapping circles and two points, start and finish. We want to produce a path of overlapping circles, starting with the one with start in it and ending with the one with end in it. There can be several potential paths, we just want to know if there's any path at all.
So here's a very basic collision checking system. Whenever you update, run Collision and pass the parameters of the circle you are checking collision for.
function Collision (x, y, r) {
for (i=0; i<circles.length; i++) {
//Distance formula
if (Math.sqrt((x-circles[i].x)(x-circles[i].x) + (y-circles[i].y)(y-circles[i].y) < r) {
return true;
}
}
Here's an example of a circle object, and how to call it:
function Circle() {
this.x = Math.random()*100;
this.y = Math.random()*100;
this.r = Math.random()*50;
this.update = function() {
if (Collision(this.x, this.y, this.r) {
console.log("circle collided with another circle");
}
}
};
Additionally, you can check out the source of a project I created that uses lots of circles and checks the collision between all of them and the player. http://betaio.bitballoon.com
Here is a more complete answer, I didn't attempt to visualize the circles so its hard for me to be sure this is totally correct but I think this should get you closer.
I think the algorithm is O(N^2) so it won't be fast but the strategy I took is to build up an index over every single overlapping circle and then find one using the point, then essentially recurse through the overlapping index to find all of the cirlces it is associated with in a group.
Here is the code:
function circleCollisionDetect (c1, c2) {
var dx = c1[0] - c2[0]
var dy = c1[1] - c2[1]
var distance = Math.sqrt(dx * dx + dy * dy)
return distance < c1[2] + c2[2]
}
function circlePointCollisionDetect (p, c) {
const dx = p[0] - c[0]
const dy = p[1] - c[1]
const distance = Math.sqrt(dx * dx + dy * dy)
return distance < c[2]
}
function search (i, circles, index) {
const group = []
function follow(i) {
if (!~group.indexOf(i)) {
group.push(i)
const overlaps = index[i]
for (let x = 0, n = overlaps.length; x < n; x++) {
follow(overlaps[x])
}
}
}
follow(i)
return group
}
const circles = [
[6, 19, 1],
[6, 11, 4],
[8, 17, 3],
[19, 19, 2],
[19, 11, 4],
[15, 7, 6],
[12, 19, 4]
]
const overlaps = []
const p = [10, 19]
// Find one that overlaps the starting point
const c = circles.find(c => circlePointCollisionDetect(p, c))
const start = circles.indexOf(c)
// Build an index of all overlapping circles
for (let a = 0, n = circles.length; a < n; a++) {
for (let b = 0; b < n; b++) {
const c1 = circles[a]
const c2 = circles[b]
if (c1 === c2) continue;
if (!overlaps[a]) overlaps[a] = []
if (circleCollisionDetect(c1, c2)) overlaps[a].push(b)
}
}
// Next search through the index recursively for unique overlapping circles
const overlapping = search(start, circles, overlaps)
console.log('start:', start)
console.log('index:', overlaps)
console.log('overlapping:', overlapping)
Which prints:
start: 2
index: [ [ 2 ], [ 2, 5 ], [ 0, 1, 6 ], [], [ 5 ], [ 1, 4 ], [ 2 ] ]
overlapping: [ 2, 0, 1, 5, 4, 6 ]
So basically they are all overlapping each other except for [19, 19, 2], is that correct?
Related
So if I have a 2D array such as
const array = [
[1, 2, 3, 4, 5],
[9, 8, 7, 6, 5],
[1, 8, 3, 6, 5],
[9, 8, 7, 6, 5],
[1, 8, 3, 6, 5],
[1, 2, 3, 4, 5]
];
which could for the point of the question be any size, square or not square.
and I want to extract 3x3 arrays out of it, for instance at 1,1 that would be const sub = [[8, 7, 6], [8, 3, 6], [8, 7, 6]]. So far so good - I can do this. However I am flattening the 2D array so that its represented as a 1D array (long story as to why), i.e
const array = [1, 2, 3, 4, 5, 9, 8, 7, 6, 5, 1, 8, 3, 6, 5, 9, 8, 7, 6, 5, 1, 8, 3, 6, 5, 1, 2, 3, 4, 5];
What I'm trying to do is extract the same (or any) 3x3 array out of this but while its represented as a 1D array, so I would then get back [8, 7, 6, 8, 3, 6, 8, 7, 6].
I almost got there, however I made an error of only working with arrays that were 9x9 and always extracting 3x3 subsets which mean that my solution only works for that specific case, and the more I stare at my solution, the more I cannot work out what the generic solution would look like.
My solution:
const extractSubsetFrom1D = (array, subHeight, subWidth, startRow, startCol) => {
const kWH = subWidth * subHeight
const subset = array.slice(((((kWH - 2) * startRow) + startCol) * kWH), ((((kWH - 2) * startRow) + startCol) * kWH) + kWH)
return subset
}
In my case subHeight and subWidth were always equalling 3 respectively, and as the array itself was always 9x9 I believe I accidentally stumbled on a solution for that specific case as they divide nicely into each other.
To be clear my solution will fail for the startRow = 1 startCol = 0 for the provided array (it works for the startRow = 0 scenario
It's not entirely clear to me how you came to your current implementation, but I can at least tell:
✅ You correctly determined the size of the sub grid array to return (kWH)
❌ You incorrectly assume you can slice out a sub grid as one continuous part of the original 1d array
🟠 The calculation of the first element seems kind-of-right but is actually wrong (probably because of the previous mistake)
From (y,x) to i
Let's start from scratch and work our way up to a nice one liner.
In a 2d-array, you can get a cell's value by doing:
cellValue = grid2d[y][x]
Once you flatten it, you'll need to do:
cellValue = grid1d[y * GRID_WIDTH + x]
y * GRID_WIDTH takes you to the start of the right row, and + x gets you to the right column.
As you can see, you need to know the original grid's size before you can even query a specific cell. That means your extract function would need an argument to pass the original width (or, if the grids are guaranteed to be square, you can do Math.sqrt(array.length).
A slice per row
Let's use this math to find the indices of a 2x2 sub grid at (1,1) extracted from a 3x3 source grid:
0 1 2
3 [4][5]
6 [7][8]
As you can see, the resulting indices are [4,5,7,8]. There is no way to slice these indices out of the source array directly because we don't want to include the 6.
Instead, we can use a nested loop to skip the gaps between our rows:
const to1d = (x, y, w) => y * w + x;
const extractSubGrid1D = (grid, gridWidth, x, y, w, h) => {
const yTop = y;
const yBottom = y + h
const xLeft = x;
const xRight = x + w;
const subgrid = [];
for (let y = yTop; y < yBottom; y += 1) {
for (let x = xLeft; x < xRight; x += 1) {
const index = to1d(x, y, gridWidth);
subgrid.push(grid[index]);
}
}
return subgrid;
}
const originalGrid = [
0, 1, 2,
3, 4, 5,
6, 7, 8
];
console.log(
extractSubGrid1D(originalGrid, 3, 1, 1, 2, 2)
)
Once you get a feel for the logic, feel free to refactor.
The other way around
To go from a 1d-index to a 2d coordinate, you can do:
x = i % w
y = Math.floor(i / w)
Applying this logic, you can also fill your sub grid like so:
Create a new array of the right size
For each of its indices, determine the original grid's (x, y) coordinate
Transform that coordinate back to an index to query the original grid with
const to1d = (x, y, w) => y * w + x;
const extractSubGrid1D = (grid, gridWidth, x, y, w, h) => Array.from(
{ length: w * h },
(_, i) => grid[to1d(x + i % w, y + Math.floor(i / w), gridWidth)]
)
const originalGrid = [
0, 1, 2,
3, 4, 5,
6, 7, 8
];
console.log(
extractSubGrid1D(originalGrid, 3, 1, 1, 2, 2)
)
I'm trying to calculate the area of a polygon giving some coordinates that can be negative.
When I pass only negative coordinates it gives me a negative area, when I pass only positives it gives me a positive but when I give it a mix ([-1, 0],[-1, 1],[1, 1],[1, 0]) it always give me a negative.
My code is:
function calculateArea(coords) {
area = 0;
points = coords.length;
j = points - 1;
for (i = 0; i < points; i++) {
area += (coords[i][0] + coords[j][0]) * (coords[i][1] - coords[j][1])
j = i;
}
return area / 2;
}
coords is an array of [x,y] coordinates.
The thing is, I'm not completely sure but I think that if I return the absolute value of the area calculated this should be correct.
Am I missing something or returning the absolute should be ok?
Area sign depends on how you order vertices not on if some coordinates are negative.
Clockwise ordering gives negative areas
Counterclockwise - positive
Assuming you have correct formula itself you could simple use Math.abs to always get positive values.
function calculateArea(coords) {
let area = 0;
for (let i = 0; i < coords.length; i++) {
const [x1, y1] = coords[i];
const [x2, y2] = coords[(i + 1) % coords.length];
area += x1 * y2 - x2 * y1
}
return area / 2;
// replace with
// return Math.abs(area) / 2;
}
console.log('Clockwise:', calculateArea([
[-1, 0],
[-1, 1],
[1, 1],
[1, 0]
]))
console.log('Counterclockwise:', calculateArea([
[-1, 0],
[-1, 1],
[1, 1],
[1, 0]
].reverse()))
So I'm trying to write a function that can iterate any matrix (square or not) diagonally with configurable left to right or right to left iteration, diagonal along which the matrix is iterated and top to bottom or bottom to top iteration. I did come up with a function that successfully iterates a matrix diagonally left to right, bottom to top along either diagonal
function iterateDiagonally(matrix, main, rightToLeft, topToBottom)
{
for(let a = 0, b = matrix.length + matrix[0].length - 1; a < b; ++a)
{
let x = main ? Math.min(a, matrix.length - 1) : Math.max(0, a - matrix[0].length + 1);
let y = main ? Math.min(matrix[0].length - 1, matrix.length + 1 - a) : Math.min(a, matrix[0].length - 1);
for(let c = 0, d = Math.min(y + 1, main ? x + 1 : matrix.length - x); c < d; ++c)
{
let eX = main ? x - c : x + c;
let eY = y - c;
console.log(matrix[eX][eY]);
}
}
}
/*
1 4 7
2 5 8
3 6 9
*/
console.log("Along the main diagonal");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], true);
console.log("Along the antidiagonal");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], false);
but realized that also making the first 2 parameters variables would make the (already bulky code) look much bulkier. Thus I'm looking for a more clean, compact and no less efficient solution. Thanks!
I've created an alternative implementation for you that's very simple.
Always start on the top row, and either start at the top or bottom depending on the boolean passed in. Then, we begin an iteration loop, always incrementing x, and either incrementing or decrementing y. On each iteration, check whether the point is out of bounds (this is the terminating condition). Then, add the value at that position to the results array.
const board = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
const pointInbounds = (x, y, board) => (
x >= 0
&& x < board[0].length
&& y >= 0
&& y < board.length
)
const diagonalTraversal = (board, bottomToTop=false) => {
let y = bottomToTop ? board.length-1 : 0,
x = 0
const values = []
while(pointInbounds(x, y, board)) {
values.push(board[y][x])
x++
y += bottomToTop ? -1 : 1
}
return values
}
diagonalTraversal(board)
By default it will traverse the board from the top left point to the bottom right. If given a true boolean as a second argument, it will start at the bottom left and traverse up to the right.
So I ended up finishing it off myself after a lot of thinking. Leaving it here if anyone comes across the same issue. It can look pretty complicated, but the idea is really simple
function iterateDiagonally(matrix, main, rightToLeft, topToBottom)
{
// Move along half of the perimeter, excluding the duplicate corner element
// a - first element of each diagonal
// b - amount of diagonals in total
for(let a = 0, b = matrix.length + matrix[0].length - 1; a < b; ++a)
{
// For the main diagonal, first move right along the x axis starting with the bottom left element, then move up the y axis
// For the antidiagonal, first move down the y axis starting with the top left element, then move right along the x axis
let x = main ? Math.min(a, matrix.length - 1) : Math.max(0, a - matrix[0].length + 1);
let y = main ? Math.min(matrix[0].length - 1, matrix.length + 1 - a) : Math.min(a, matrix[0].length - 1);
// Invert the starting position and direction of all movement
if(rightToLeft) x = matrix.length - 1 - x, y = matrix[0].length - 1 - y;
let diagonal = [];
// Move along each diagonal
// c - element of diagonal
// d - amount of elements in diagonal in total
for(let c = 0, d = Math.min(rightToLeft ? matrix[0].length - y : y + 1, main !== rightToLeft ? x + 1 : matrix.length - x); c < d; ++c)
{
// Invert the diagonal traversal order
let iC = topToBottom !== rightToLeft ? d - 1 - c : c;
// X coordinate of the element
let eX = main !== rightToLeft ? x - iC : x + iC;
// Y coordinate of the element
let eY = rightToLeft ? y + iC : y - iC;
diagonal.push(matrix[eX][eY]);
}
console.log(diagonal.join(", "));
}
}
/*
1 4 7
2 5 8
3 6 9
*/
console.log("Parallel main diagonal, left to right, bottom to top");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], true, false, false);
console.log("Parallel main diagonal, right to left, bottom to top");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], true, true, false);
console.log("Parallel main diagonal, left to right, top to bottom");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], true, false, true);
console.log("Parallel main diagonal, right to left, top to bottom");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], true, true, true);
console.log("Parallel antidiagonal, left to right, bottom to top");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], false, false, false);
console.log("Parallel antidiagonal, right to left, bottom to top");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], false, true, false);
console.log("Parallel antidiagonal, left to right, top to bottom");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], false, false, true);
console.log("Parallel antidiagonal, right to left, top to bottom");
iterateDiagonally([[1, 2, 3], [4, 5, 6], [7, 8, 9]], false, true, true);
I have two vectors, the Y-aligned is fixed whereby the X-aligned is allowed to rotate. These vectors are connected together through two fixed-length segments. Given the angle between the two vectors (82.74) and the length of all segments, how can I get the angle of the two jointed segments (24.62 and 22.61)?
What is given: the magnitude of the vectors, and the angle between the X-axis and OG:
var magOG = 3,
magOE = 4,
magGH = 3,
magEH = 2,
angleGamma = 90;
This is my starting point: angleGamma = 90 - then, I will have following vectors:
var vOG = new vec2(-3,0),
vOE = new vec2(0,-4);
From here on, I am trying to get angleAlphaand angleBeta for values of angleGamma less than 90 degrees.
MAGNITUDE OF THE CONSTRAINED SEGMENTS:
Segments HG and HE must meet following conditions:
/
| OG*OG+ OE*OE = (HG + HE)*(HG + HE)
>
| OG - HG = OE - HE
\
which will lead to following two solutions (as pointed out in the accepted answer - bilateration):
Solution 1:
========================================================
HG = 0.5*(-Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(-Math.sqrt(OG*OG + OE*OE) - OG + OE)
Solution 2:
========================================================
HG = 0.5*(Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(Math.sqrt(OG*OG + OE*OE) - OG + OE)
SCRATCHPAD:
Here is a playground with the complete solution. The visualization library used here is the great JSXGraph. Thanks to the Center for Mobile Learning with Digital Technology of the Bayreuth University.
Credits for the circle intersection function: 01AutoMonkey in the accepted answer to this question: A JavaScript function that returns the x,y points of intersection between two circles?
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function rad2deg(rad) {
return rad * 180 / Math.PI;
}
function lessThanEpsilon(x) {
return (Math.abs(x) < 0.00000000001);
}
function angleBetween(point1, point2) {
var x1 = point1.X(), y1 = point1.Y(), x2 = point2.X(), y2 = point2.Y();
var dy = y2 - y1, dx = x2 - x1;
var t = -Math.atan2(dx, dy); /* range (PI, -PI] */
return rad2deg(t); /* range (180, -180] */
}
function circleIntersection(circle1, circle2) {
var r1 = circle1.radius, cx1 = circle1.center.X(), cy1 = circle1.center.Y();
var r2 = circle2.radius, cx2 = circle2.center.X(), cy2 = circle2.center.Y();
var a, dx, dy, d, h, h2, rx, ry, x2, y2;
/* dx and dy are the vertical and horizontal distances between the circle centers. */
dx = cx2 - cx1;
dy = cy2 - cy1;
/* angle between circle centers */
var theta = Math.atan2(dy,dx);
/* vertical and horizontal components of the line connecting the circle centers */
var xs1 = r1*Math.cos(theta), ys1 = r1*Math.sin(theta), xs2 = r2*Math.cos(theta), ys2 = r2*Math.sin(theta);
/* intersection points of the line connecting the circle centers */
var sxA = cx1 + xs1, syA = cy1 + ys1, sxL = cx2 - xs2, syL = cy2 - ys2;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r1 + r2)) {
/* no solution. circles do not intersect. */
return [[sxA,syA], [sxL,syL]];
}
thetaA = -Math.PI - Math.atan2(cx1,cy1); /* Swap X-Y and re-orient to -Y */
xA = +r1*Math.sin(thetaA);
yA = -r1*Math.cos(thetaA);
ixA = cx1 - xA;
iyA = cy1 - yA;
thetaL = Math.atan(cx2/cy2);
xL = -r2*Math.sin(thetaL);
yL = -r2*Math.cos(thetaL);
ixL = cx2 - xL;
iyL = cy2 - yL;
if(d === 0 && r1 === r2) {
/* infinite solutions. circles are overlapping */
return [[ixA,iyA], [ixL,iyL]];
}
if (d < Math.abs(r1 - r2)) {
/* no solution. one circle is contained in the other */
return [[ixA,iyA], [ixL,iyL]];
}
/* 'point 2' is the point where the line through the circle intersection points crosses the line between the circle centers. */
/* Determine the distance from point 0 to point 2. */
a = ((r1*r1) - (r2*r2) + (d*d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = cx1 + (dx * a/d);
y2 = cy1 + (dy * a/d);
/* Determine the distance from point 2 to either of the intersection points. */
h2 = r1*r1 - a*a;
h = lessThanEpsilon(h2) ? 0 : Math.sqrt(h2);
/* Now determine the offsets of the intersection points from point 2. */
rx = -dy * (h/d);
ry = +dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx, yi = y2 + ry;
var xi_prime = x2 - rx, yi_prime = y2 - ry;
return [[xi, yi], [xi_prime, yi_prime]];
}
function plot() {
var cases = [
{a: 1.1, l: 1.9, f: 0.3073},
{a: 1.0, l: 1.7, f: 0.3229}
];
var testCase = 1;
var magA = cases[testCase].a, magL = cases[testCase].l;
var maxS = Math.sqrt(magA*magA+magL*magL), magS1 = maxS * cases[testCase].f, magS2 = maxS - magS1;
var origin = [0,0], board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5.0, 5.0, 5.0, -5.0], axis: true});
var drawAs = {dashed: {dash: 3, strokeWidth: 0.5, strokeColor: '#888888'} };
board.suspendUpdate();
var leftArm = board.create('slider', [[-4.5, 3], [-1.5, 3], [0, -64, -180]]);
var leftLeg = board.create('slider', [[-4.5, 2], [-1.5, 2], [0, -12, -30]]);
var rightArm = board.create('slider', [[0.5, 3], [3.5, 3], [0, 64, 180]]);
var rightLeg = board.create('slider', [[0.5, 2], [3.5, 2], [0, 12, 30]]);
var lh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(leftArm.Value())); },
function() { return -magA * Math.cos(deg2rad(leftArm.Value())); }
], {size: 3, name: 'lh'});
var LA = board.create('line', [origin, lh], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS1 = board.create('circle', [function() { return [lh.X(), lh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var lf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(leftLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(leftLeg.Value())); }
], {size: 3, name: 'lf'});
var LL = board.create('line', [origin, lf], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS2 = board.create('circle', [function() { return [lf.X(), lf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var lx1 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[0][0]; },
function() { return circleIntersection(cLS1, cLS2)[0][1]; }
], {size: 3, face:'x', name: 'lx1'});
var lx2 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[1][0]; },
function() { return circleIntersection(cLS1, cLS2)[1][1]; }
], {size: 3, face:'x', name: 'lx2'});
/* Angle between lh, lx1 shall be between 0 and -180 */
var angleLAJ = board.create('text', [-3.7, 0.5, function(){ return angleBetween(lh, lx1).toFixed(2); }]);
/* Angle between lf, lx1 shall be between 0 and 180 */
var angleLLJ = board.create('text', [-2.7, 0.5, function(){ return angleBetween(lf, lx1).toFixed(2); }]);
var rh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(rightArm.Value())); },
function() { return -magA * Math.cos(deg2rad(rightArm.Value())); }
], {size: 3, name: 'rh'});
var RA = board.create('line', [origin, rh], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS1 = board.create('circle', [function() { return [rh.X(), rh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var rf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(rightLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(rightLeg.Value())); }
], {size: 3, name: 'rf'});
var RL = board.create('line', [origin, rf], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS2 = board.create('circle', [function() { return [rf.X(), rf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var rx1 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[1][0]; },
function() { return circleIntersection(cRS1, cRS2)[1][1]; }
], {size: 3, face:'x', name: 'rx1'});
var rx2 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[0][0]; },
function() { return circleIntersection(cRS1, cRS2)[0][1]; }
], {size: 3, face:'x', name: 'rx2'});
var angleRAJ = board.create('text', [+1.3, 0.5, function(){ return angleBetween(rh, rx1).toFixed(2); }]);
var angleRLJ = board.create('text', [+2.3, 0.5, function(){ return angleBetween(rf, rx1).toFixed(2); }]);
board.unsuspendUpdate();
}
plot();
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraph.css" />
<link rel="stylesheet" href="style.css">
<script type="text/javascript" charset="UTF-8" src="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraphcore.js"></script>
</head>
<body>
<div id="jxgbox" class="jxgbox" style="width:580px; height:580px;"></div>
</body>
</html>
According to your sketch, the coordinates of E and G are:
E = (0, -magOE)
G = magOG * ( -sin(gamma), -cos(gamma) )
Then, calculating the position of H is a trilateration problem. Actually, it is just bilateration because you are missing a third distance. Hence, you will get two possible positions for H.
First, let us define a new coordinate system, where E lies at the origin and G lies on the x-axis. The x-axis direction in our original coordinate system is then:
x = (G - E) / ||G - E||
The y-axis is:
y = ( x.y, -x.x )
The coordinates of E and G in this new coordinate system are:
E* = (0, 0)
G* = (0, ||G - E||)
Now, we can easily find the coordinates of H in this coordinate system, up to the ambiguity mentioned earlier. I will abbreviate ||G - E|| = d like in the notation used in the Wikipedia article:
H.x* = (magGH * magGH - magEH * magEH + d * d) / (2 * d)
H.y* = +- sqrt(magGH * magGH - H.x* * H.x*)
Hence, we have two solutions for H.y, one positive and one negative.
Finally, we just need to transform H back into our original coordinate system:
H = x * H.x* + y * H.y* - (0, magOE)
Given the coordinates of H, calculating the angles is pretty straightforward:
alpha = arccos((H.x - G.x) / ||H - G||)
beta = arccos((H.y - E.y) / ||H - E||)
Example
Taking the values from your example
magOG = 3
magOE = 4
magGH = 3
magEH = 2
angleGamma = 82.74°
we first get:
E = (0, -4)
G = 3 * ( -sin(82.74°), -cos(82.74°) )
= (-2.976, -0.379)
Our coordinate system:
x = (-0.635, 0.773)
y = ( 0.773, 0.635)
In this coordinate system:
E* = (0, 0)
G* = (0, 4.687)
Then, the coordinates of H in our auxiliary coordinate system are:
H* = (2.877, +- 0.851)
I will only focus on the positive value for H*.y because this is the point that you marked in your sketch.
Transform back to original coordinate system:
H = (-1.169, -1.237)
And finally calculate the angles:
alpha = 25.41°
beta = 22.94°
The slight differences to your values are probably caused by rounding errors (either in my calculations or in yours).
I need to find a library that allows me to get interpolated values from irregular 2d data. Imagine having something like this:
var data = [{{x: 0, y: 0, value: 0},
{x: 0.5, y: 1, value: 1},
{x: 1, y: 0, value: 2}}, .... Many more elements]
var value = interpolate(data, 0.24, 0.3); // 0.24 = x, 0.3 = y
What the interpolate method does is that it finds the element, in this case a triangle, that the coordinate is inside. Then it interpolates the value between the corners of the element it is contained in.
I do realize that there are lots of aspects in it to optimize performance like building up a tree that allows fast narrowing of elements by having preprocessed bounding boxes. All of this would be great as well, but I am just trying to get started.
There must be some library out there that I can use for it instead of writing my own.
Since search results for barycentric interpolation in javascript were inconclusive, here's some code that might help you get started.
This code takes as input a data set of 2D points, each with a "value", and a "new point" with an unknown value. It first finds the smallest triangle in the data set that contains the "new point", then performs barycentric interpolation using that triangle to find a value for the "new point".
This runs reasonably quickly with a data set of a few hundred points. There are many opportunities for testing, error checking, and optimization - for example, don't look at every possible triangle in the data set. N choose 3 grows with the cube of N, so optimizing to look at triangles made with only points "close to" the "new point" could show significant performance gains.
// Calculate the area of a triangle
function triangle_area(vertexA, vertexB, vertexC) {
return Math.abs(((vertexA.x - vertexC.x) * (vertexB.y - vertexA.y) - (
vertexA.x - vertexB.x) * (vertexC.y - vertexA.y)) * 0.5)
}
// Given a number N, return a list of all possible triples from the list [1..N]
// credit: http://stackoverflow.com/a/5752056/1612562
function list_triples(N) {
var fn = function(n, src, got, all) {
if (n == 0) {
if (got.length > 0) {
all[all.length] = got;
}
return;
}
for (var j = 0; j < src.length; j++) {
fn(n - 1, src.slice(j + 1), got.concat([ src[j] ]), all);
}
return;
}
var triples = [];
// Generates the list [0, ..., N]
// credit: http://stackoverflow.com/a/20066663/1612562
var indices =
Array.apply(null, {length: N}).map(Number.call, Number);
fn(3, indices, [], triples);
return triples;
}
// Given three vertices of a triangle and a point, determine if
// the point falls in the triangle
// credit: https://koozdra.wordpress.com/2012/06/27/javascript-is-point-in-triangle/
// credit: http://www.blackpawn.com/texts/pointinpoly/default.html
function is_in_triangle(newPoint, vertexA, vertexB, vertexC) {
var v0 = [vertexC.x - vertexA.x, vertexC.y - vertexA.y];
var v1 = [vertexB.x - vertexA.x, vertexB.y - vertexA.y];
var v2 = [newPoint.x - vertexA.x, newPoint.y - vertexA.y];
var dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]);
var dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]);
var dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]);
var dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]);
var dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]);
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return ((u >= 0) && (v >= 0) && (u + v < 1));
}
// Perform barycentric interpolation on a point in a triangle
function barycentric_interpolate(newPoint, vertexA, vertexB, vertexC) {
var area = triangle_area(vertexA, vertexB, vertexC);
var sub_area_1 = triangle_area(newPoint, vertexB, vertexC);
var sub_area_2 = triangle_area(vertexA, newPoint, vertexC);
var sub_area_3 = triangle_area(vertexA, vertexB, newPoint);
return ((sub_area_1 * vertexA.v) + (sub_area_2 * vertexB.v) + (sub_area_3 *
vertexC.v)) / area;
}
// Find the smallest triangle in the data set containing the new
// point, and perform barycentric interpolation using that triangle
function interpolate(newPoint, data) {
var triangles = list_triples(data.length);
var smallest_triangle_area = Number.MAX_VALUE;
var smallest_triangle;
for (t in triangles) {
var vertexA = data[triangles[t][0]];
var vertexB = data[triangles[t][1]];
var vertexC = data[triangles[t][2]];
var in_triangle = is_in_triangle(newPoint, vertexA, vertexB, vertexC);
if (in_triangle) {
if (triangle_area(vertexA, vertexB, vertexC) < smallest_triangle_area) {
smallest_triangle = [vertexA, vertexB, vertexC];
}
}
}
return smallest_triangle
? barycentric_interpolate(newPoint, smallest_triangle[0], smallest_triangle[1], smallest_triangle[2])
: "Interpolation failed: newPoint isn't in a triangle";
}
var newPoint = {'x': 0.24, 'y': 0.3};
var data = [
{'x': 0, 'y': 0, 'v': 0},
{'x': 0.5, 'y': 1, 'v': 1},
{'x': 1, 'y': 0, 'v': 2},
{'x': 1.5, 'y': 2.5, 'v': 1.5},
{'x': 2, 'y': 1, 'v': 0.5}
];
console.log(interpolate(newPoint, data));
There are also other kinds of spatial interpolation, e.g. kriging, which does have at least one ready-made .js library.