I am having a problem looping through an array of point objects, comparing each object to the other objects, and pushing it to a subarray. The main problem is with the iteration using the for loop. First, I have a set of points:
var points = [
{ id: 1, x: 0.0, y: 0.0 },
{ id: 2, x: 10.1, y: -10.1 },
{ id: 3, x: -12.2, y: 12.2 },
{ id: 4, x: 38.3, y: 38.3 },
{ id: 5, x: 79.0, y: 179.0 }
];
I then want to compare each point to ALL the other points. Apparently, my method is just comparing the i to the j that's next in line in the array. What I want is a subarray for each point object that has the objects id, the id of the point object it's being compared to, and the distance between those 2 points. Ex output: [{1, 2, 12.74423}, {1, 2, 10.76233), {1, 3, 43.23323}, {1, 4, 23.45645}, {1, 5, 127.43432}]; Here is my code, and below that is the output I get in my console. What am I doing wrong here? Note: I put in some random console.logs to see what was going on.
var pointPairs = [];
for (let i = 0; i < points.length; i = i + 1) {
var p1 = points[i];
for (let j = i + 1; j < points.length; j = j + 1) {
var p2 = points[j];
var distance = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
pointPairs.push({ p1: p1.id, p2: p2.id, distance });
console.log(pointPairs);
};
};
Results:
0:{p1: 1, p2: 2, distance: 14.28355697996826}
1:{p1: 1, p2: 3, distance: 17.253405460951758}
2:{p1: 1, p2: 4, distance: 54.16437943888954}
3:{p1: 1, p2: 5, distance: 195.65786465153911}
4:{p1: 2, p2: 3, distance: 31.536962440920014}
5:{p1: 2, p2: 4, distance: 56.01606912306503}
6:{p1: 2, p2: 5, distance: 201.26107422946941}
7:{p1: 3, p2: 4, distance: 56.84593213238745}
8:{p1: 3, p2: 5, distance: 190.10439237429526}
9:{p1: 4, p2: 5, distance: 146.46835835770128}
Pointy is right about needing to start at zero in your second loop.
If you just need to iterate through a list, I recommend avoiding for-loops. They are verbose and error-prone, compared to the built-in array iteration method (Array.forEach). Here is your code, converted to use Array.forEach. I think you'll agree that it's much simpler.
var points = [
{ id: 1, x: 0.0, y: 0.0 },
{ id: 2, x: 10.1, y: -10.1 },
{ id: 3, x: -12.2, y: 12.2 },
{ id: 4, x: 38.3, y: 38.3 },
{ id: 5, x: 79.0, y: 179.0 }
]
var pointPairs = [];
points.forEach((p1, i) => {
points.forEach((p2, j) => {
var distance = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
pointPairs.push({ p1: p1.id, p2: p2.id, distance });
console.log(pointPairs);
})
})
It does appear that your algorithm is working, if the goal is to make a single comparison between any two points - i.e. if p1 and p2 are considered unordered - a combination, not a permutation.
However, if you wish that p1 and p2 contain every possible permutation, not just combination, then start j at 0 and skip the iteration where i===j:
var pointPairs = [];
for (let i = 0; i < points.length; i = i + 1) {
var p1 = points[i];
innerLoop: for (let j = 0; j < points.length; j = j + 1) {
if (j===i) continue innerLoop;
var p2 = points[j];
var distance = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
pointPairs.push({ p1: p1.id, p2: p2.id, distance });
console.log(pointPairs);
};
};
(The innerLoop label is not strictly needed, but helps clarify the code when you have nested loops)
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 have random rectangles on canvas stored in an array like so:
var rectangles = [
{x: 10, y: 10},
{x: 40, y: 50},
{x: 1, y: 70},
{x: 80, y: 5},
{x: 30, y: 60}
];
I now want to label these rectangles based on their proximity to the origin point (0, 0).
My first thought was to loop through the x and y axis in different patterns, one example would be:
// 100 is the width and height of the canvas
for(var x = 0; x < 100; x++){
for(var y = 0; y < 100; y++){
// "intersects" loops through the array and returns the matching index or -1 if no match
if(intersects(rectangles, x, y) > -1){
console.log('Rectangle' + (intersects(rectangles, x, y) + 1));
}
}
}
The issue i am having, is that no matter the pattern of the loop the result is not as expected.
My second thought was to draw rectangles to the origin point (seen on the last image) and sort the by the size of the rectangle. However, this (and calculating the line distance for that matter) also did not produce the expected result. This can be seen with the green rectangle, that is very close to X0, but should be last.
For example this should return the same result:
Does anyone know how I can achieve the correct labeling result? Thanks!
Here's how to compare distances of coordinates against the origin and sort them (closest to furthest).
var rectangles = [
{x: 10, y: 10},
{x: 40, y: 50},
{x: 1, y: 70},
{x: 80, y: 5},
{x: 30, y: 60}
];
const sumOfSquares = (x, y) => {
return Math.pow(x, 2) + Math.pow(y, 2);
};
rectangles.sort((a, b) => {
const sumA = sumOfSquares(a.x, a.y);
const sumB = sumOfSquares(b.x, b.y);
return sumA - sumB;
});
console.log(rectangles);
Let's suppose I have the following grid and each cell of the grid has an index mapped to a 1d array.
0, 1, 2
3, 4, 5,
6, 7, 8
I could represent this with a 1d array like: [0, 1, 2, 3, 4, 5, 6, 7, 8]
I would like to know a simple way to map a 2d coordinate like (3,1) to its index in the array, in this case, would be 2.
After researching a lot, I found a lot of people suggesting this equation: index = x + (y * width), but it doesn't seem to work in my tests.
For example for (1, 1), the result would be index = 1 + (1 * 3) = 4, and for (3, 1) would be index = 3 + (1 * 3) = 6, which does not make any sense to me.
Is it possible to achieve this in a simple way? Or I would need to use iterators like a for?
2D matrix notation is commonly (row, col), with indexes starting at 0.
Thus, (3, 1) is invalid: only 3 rows, from 0 to 2. (1, 1) means 2nd row, 2nd colum, which is 4 in your example. The formula is thus:
(row * width) + col
(2, 1) = 2*3+1 = index 7
once again using 0 for the first row/col.
If you really want to keep thinking with indexes starting at one, just change the formula to:
((row - 1) * width) + (col - 1) = 1D index
In your case it would be index = (x - 1) + ((y - 1) * width) as your coordinate system starts from 1 and arrays start from 0.
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
function getPosition(x, y, width) {
return x - 1 + (y - 1) * width;
}
console.log({
position: getPosition(3, 1, 3),
element: arr[getPosition(3, 1, 3)]
});
It is indeed index = x + y * width (the parens are unnecessary) or index = y + x * width, depending on whether you want your flat array to keep the rows together as in your question ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], index = x + y * width), or keep columns together ([0, 3, 6, 1, 4, 7, 2, 5, 8], index = y + x * width). But indexes usually start at 0, not 1. So your (1, 1) would be (0, 0) and your (3, 1) would be (2, 0).
Here's the first:
// 0, 1, 2
// 3, 4, 5
// 6, 7, 8
const a = [0, 1, 2, 3, 4, 5, 6, 7, 8];
let x = 0, y = 1;
let index = x + y * 3;
console.log(`(${x}, ${y}) is index ${index}, value ${a[index]}`);
x = 2;
y = 0;
index = x + y * 3;
console.log(`(${x}, ${y}) is index ${index}, value ${a[index]}`);
Here's the second:
// 0, 1, 2
// 3, 4, 5
// 6, 7, 8
const a = [0, 3, 6, 1, 4, 7, 2, 5, 8];
let x = 0, y = 1;
let index = y + x * 3;
console.log(`(${x}, ${y}) is index ${index}, value ${a[index]}`);
x = 2;
y = 0;
index = y + x * 3;
console.log(`(${x}, ${y}) is index ${index}, value ${a[index]}`);
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?
I'm creating an isometric tile map for a small www game, I have created an array to store map data, column and row numbers aswell as height and width of tile. Then I found function getTile on web which is on the end of this map array. It should be giving me value of the tile later on. It looks like this:
var map = {
cols: 13,
rows: 11,
twidth: 200,
theight: 100,
tiles: [
1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1,
1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1,
1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 1,
1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1,
2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1,
],
getTile: function(col, row) {
return this.tiles[row * map.cols + col]
}
};
Then I have a code to draw something on the canvas:
for (var c = 0; c < map.cols; c++) {
for (var r = 0; r < map.rows; r++) {
var tile = map.getTile(c, r);
var screenX = (c - r) * map.twidth/2;
var screenY = (c + r) * map.theight/2;
var screenX = screenX+1000;
if(tile == 1) {
var element = "area_grass_01";
}
if(tile == 2) {
var element = "area_road_01";
}
var img = document.getElementById(element);
ctxObj.drawImage(img, (tile - 1) * map.twidth, 0, map.twidth, map.theight, screenX, screenY, map.twidth, map.theight);
}
}
Now, when I run console_log or alert with tile variable in it when the loop is run. It shows all the numbers that are included in map.tiles one by one. However when I try to find out which image should be drawn like this:
if(tile == 1) {
var element = "area_grass_01";
}
if(tile == 2) {
var element = "area_road_01";
}
var img = document.getElementById(element);
It only draws the title with value 1. Rest stays undrawn. Like this:
Map render
Now I want to ask you how do I actually set the image according to the array number I've put inside map.tiles?
And another question that I have to anyone familiar with this. If I have a tile width 200 and height 100, how do I draw let's say a building which is much higher? Do I find the height of that drawing and set it higher by the drawing size - tile height (which is 100), or do you have any other advice on how to draw higher elements? And do I still use the same drawing technique for this:
var screenX = (c - r) * map.twidth/2;
var screenY = (c + r) * map.theight/2;
But with adjustment of map.theight in screenY?
You are using an overload of drawImage of which the second and third parameters are the source X and Y values. You only need to use these if you are making use of a spritesheet which it doesn't seem like you are. Try replacing the second parameter of that function call with just 0.
Take a look at the sx, sy, swidth and sheight parameters here for more information.