Optimise object overlapping detection (Javascript) - javascript

Issue
I am writing a program which involves calculating how many pixels one moving object overlaps the other. I'd have to return this value many times a second, so the program would have to be efficient. The example that I have come up with seems not to be.
Example
Let's scale-down for a minute and imagine we have an object that is 3*3 pixels and one that is 3*2
a b c
d e f j k l
g h i m n o
Each letter represents an individual pixel of each object. The 3*3 object sits on the left, and the 3*2 object sits on the right, with an x value 4 greater than that of the larger object. They are not overlapping.
Code
Currently, I am returning the number of overlapping pixels through a simple function that checks every pixel in object one against every pixel in object two for overlaps:
var a = {
width: 3,
height: 3,
x: 0,
y: 0
}
var b = {
width: 3,
height: 2,
x: 4,
y: 0
}
function overlappingPixels(object_1, object_2) {
var overlapping = 0;
for (var w_1 = 0; w_1 < object_1.width; w_1++) {
for (var h_1 = 0; h_1 < object_1.height; h_1++) {
for (var w_2 = 0; w_2 < object_1.width; w_2++) {
for (var h_2 = 0; h_2 < object_1.height; h_2++) {
if (w_1 + object_1.x == w_2 + object_2.x && h_1 + object_1.y == h_2 + + object_2.y) {
overlapping++;
}
}
}
}
}
return overlapping;
}
overlappingPixels(a, b); returns 0, because the two objects have no overlapping pixels.
Recap
To recap, I have built a function that compares each pixel of object one to each pixel of object two for any overlaps. This seems horribly inefficient, and I was curious as to whether there was a quicker option if this calculation needed to be performed very quickly for moving objects. The speed of the function breaks down quickly as the size of the objects increase. I'd be performing this calculation on larger objects anyway, so this isn't ideal.
Thanks!

There is an easy and efficient way to check if two rectangles collide.
var rect1 = {x: 5, y: 5, width: 50, height: 50}
var rect2 = {x: 20, y: 10, width: 10, height: 10}
if (rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.height + rect1.y > rect2.y) {
// collision detected!
}
See MDN 2D object collision detection
To get the size of overlap is also quite easy once you know there is collision for sure. Just get the heigth and width where they overlap, and get the area by multiplying them. See the calculateCollisionLength function in the snippet to see how you can calculate the overlap without going over it pixel by pixel.
const calculateCollisionLength = (point1, point2, length1, length2) => {
const pointb1 = point1 + length1;
const pointb2 = point2 + length2;
const diff1 = Math.abs(point1 - point2);
const diff2 = Math.abs(pointb1 - pointb2);
return (length1 + length2 - diff1 - diff2) / 2;
}
function checkCollusion(rect1, rect2) {
if (rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.height + rect1.y > rect2.y) {
// collision detected!
const collision = { xLength: 0, yLength: 0 };
collision.xLength = calculateCollisionLength(rect1.x, rect2.x, rect1.width, rect2.width);
collision.yLength = calculateCollisionLength(rect1.y, rect2.y, rect1.height, rect2.height);
return collision.xLength * collision.yLength;
}
else return null;
}
var rect1 = { x: 5, y: 5, width: 50, height: 50 }
var rect2 = { x: 20, y: 10, width: 10, height: 10 }
console.log(checkCollusion(rect1, rect2))

Related

How can I find the exact coordinates of a moving element in a canvas?

I'm building a snake game with 2 snakes, so typically, when one snake touches the other, the game should restart. However, what I have right now only checks for the entire x/y line that snake 2 is on. For example, if snake 2 were to be moving to the right/horizontally (they move infinitely because the walls are only portals that return the snake to the side they started on) and snake 2 moved up, it would eventually intercept the y line that snake 1 was moving on.Even though the snake on the left(1) isn't going to directly hit the snake on the right(2), it is about to die because it is going to cross the line that snake 2 is moving on
This is how the snake was made:
var grid = 16;
var grid2 = 16;
var count = 0;
var count2= 0;
var snake2 = {
x: 160,
y: 160,
dx: grid2,
dy: 0,
cells: [],
maxCells: 4,
};
var apple2 = {
x: 32,
y: 320
};
var snake = {
x: 160,
y: 160,
// snake velocity. moves one grid length every frame in either the x or y direction
dx: grid,
dy: 0,
// keep track of all grids the snake body occupies
cells: [],
// length of the snake. grows when eating an apple
maxCells: 4
};
//In this case the apple does not matter
var apple = {
x: 32,
y: 320
};
// get random whole numbers in a specific range
// #see https://stackoverflow.com/a/1527820/2124254
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
// game loop
function loop() {
requestAnimationFrame(loop);
// slow game loop to 15 fps instead of 60 (60/15 = 4)
if (++count < 4) {
return;
}
count = 0;
context.clearRect(0,0,canvas.width,canvas.height);
// move snake by it's velocity
snake.x += snake.dx;
snake.y += snake.dy;
snake2.x += snake2.dx;
snake2.y += snake2.dy;
// wrap snake position horizontally on edge of screen
if (snake.x < 0) {
snake.x = canvas.width - grid;
}
else if (snake.x >= canvas.width) {
snake.x = 0;
}
// wrap snake position vertically on edge of screen
if (snake.y < 0) {
snake.y = canvas.height - grid;
}
else if (snake.y >= canvas.height) {
snake.y = 0;
}
if (snake2.x < 0) {
snake2.x = canvas.width - grid;
}
else if (snake2.x >= canvas.width) {
snake2.x = 0;
}
// wrap snake position vertically on edge of screen
if (snake2.y < 0) {
snake2.y = canvas.height - grid;
}
else if (snake2.y >= canvas.height) {
snake2.y = 0;
}
// keep track of where snake has been. front of the array is always the head
snake.cells.unshift({x: snake.x, y: snake.y});
snake2.cells.unshift({x: snake2.x, y: snake2.y});
// remove cells as we move away from them
if (snake.cells.length > snake.maxCells) {
snake.cells.pop();
}
if (snake2.cells.length > snake2.maxCells) {
snake2.cells.pop();
}
//Here the snake checks for collision with itself and with the 2nd snake
if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y || cell.x === snake.cells[i].x && cell.y === snake2.cells[i].y)
The beginning of the code is just to check if snake 1 crossed paths with itself/ran into itself. After the OR statement is where it checks for snake 1 intercepting snake 2.
I attempted to tweak my code so it would look for the exact coordinate the snake is moving on, like this:
if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y || cell.xy === snake.cells[i].xy && cell.xy === snake2.cells[i].xy)
I put xy to check for both coordinates to try to create one coordinate point to look for. However, this only resulted in immediate death.
Does anyone have an idea of how to fix this? EDIT What else is needed?

Simplify if statement condition

I am making a function to check the dimensions of an item to see if it will fit in a certain box. The problem I am having is how long the if conditional statement is. for example;
item's dimensions are 7x3x2, box's dimension are 7x5x3.
if(l <= 7 && w <= 5 && h <= 3
|| l <= 7 && w <= 3 && h <= 5
|| l <= 5 && w <= 7 && h <= 3
|| l <= 5 && w <= 3 && h <= 7
|| l <= 3 && w <= 5 && h <= 7
|| l <= 3 && w <= 7 && h <= 5) {
console.log("your item fits in this box!");
} else {
...
}
Is there a way to cover every possible combination instead of writing 6 different ways on the if statement?
Order the length, width, and height from highest to lowest first, then compare once:
const item1 = { l: 3, w: 8, h: 5 };
const item2 = { l: 2, w: 3, h: 9};
const item3 = { l: 3, w: 7, h: 5};
function orderDims(l, w, h) {
const length = Math.max(l, w, h);
const width = Math.max(Math.min(l, w), Math.min(Math.max(l, w), h));
const height = Math.min(l, w, h);
return [length, width, height];
}
function itemFits(l, w, h) {
const dimArr = orderDims(l, w, h);
return dimArr[0] <=7 && dimArr[1] <= 5 && dimArr[2] <= 3;
}
console.log(itemFits(item1['l'], item1['w'], item1['h']));
console.log(itemFits(item2['l'], item2['w'], item2['h']));
console.log(itemFits(item3['l'], item3['w'], item3['h']));
You could sort their values and compare like this:
const x = 7;
const y = 3;
const z = 5;
console.log(JSON.stringify([x, y, z].sort()) === JSON.stringify([3, 5, 7]));
So your if statement could look like this:
if (JSON.stringify([l, w, h].sort()) === JSON.stringify([3, 5, 7])) {
...
}
You could refactor it like this:
class Container {
constructor(...dimensions) {
this.dimensions = dimensions;
}
fits(l, w, h) {
return JSON.stringify([l, w, h].sort()) === JSON.stringify(this.dimensions);
}
}
const container = new Container(3, 5, 7);
console.log(container.fits(3, 5, 7));
console.log(container.fits(3, 7, 5));
console.log(container.fits(5, 3, 7));
console.log(container.fits(5, 7, 3));
console.log(container.fits(7, 3, 5));
console.log(container.fits(7, 5, 3));
I think you would want to avoid extended if statements like that just for readability and code maintenance purposes? You could achieve the same logic written slightly different. Something like (pseudocode):
let fits = true;
if (x > 7){
fits = false;
}
if (y > 5){
fits = false;
}
if (z > 3) {
fits = false;
}
return fits;
You can solve this question easily, if you find the volume of the item and the box and then compare the volumes.
For example:
item's dimensions - 7x3x2
Box's dimensions - 7x5x3
Item volume - 42
Box volume - 105
Since volume of item is less than volume of box the, item can be fitted inside the box.
Using only one if else you can easily solve the question.

Check amount of 'overlap' of two sides of adjacent rectangles

I have two rectangles that are guaranteed not to overlap.
However, I need to know if they are next to each other and if they are that they touch each other by two or more units. For example, the following rectangles touch at 4 units.
In the above example I am comparing the following:
rect1 = {
x: 4,
y: 4,
width: 3,
height: 5
}
and
rect2 = {
x: 7,
y: 1,
width: 4,
height: 7
}
I am currently trying to accomplish this by several nested if/else statements, but I know there would be a more adequate way.
What is an efficient way of accomplishing this?
Disclaimer:
I used the term overlap in the title as I wasn't sure how else to describe it even though they do not actually overlap.
Here's a simple version:
function rectOverlaps2(r1, r2) {
let overlapX, overlapY;
if ( r1.x <= r2.x ) {
overlapX = r1.x + r1.width - r2.x;
} else {
overlapX = r2.x + r2.width - r1.x;
}
if ( r1.y <= r2.y ) {
overlapY = r1.y + r1.height - r2.y;
} else {
overlapY = r2.y + r2.height - r1.y;
}
return (overlapX == 0 && overlapY >= 2) || (overlapY == 0 && overlapX >= 2);
}
Added: Just realized that there is an edge case - if the overlapping dimension (width or height) of one rectangle is just 1 unit, then the overlap should not be counted. Since this looks like a homework question, fixing this will be left as an exercise to the reader.
here's my solution for this problem. My idea is to create an array that contains all numbers between the start of the rectangle and its end in the Y direction, for example. Then if the other rectangle contains any of the indices in between, that means they are 'overlapping'
I didn't try any performance optimization, but that's the general idea.
rect1 = {
x: 4,
y: 4,
width: 3,
height: 5
}
rect2 = {
x: 7,
y: 1,
width: 4,
height: 7
}
function createMap(start, end) {
return Array(end - start + 1).fill().map((_, idx) => start + idx)
}
function getEndPoints(rect){
const endX = rect.x + rect.width;
const endY = rect.y + rect.height;
return {endX, endY};
}
function getOverlap(r1, r2){
const r1Range = createMap(r1.y, r1.endY);
const r2Range = createMap(r2.y, r2.endY);
const verticalOverlap = r1Range.filter(val => !r2Range.includes(val));
console.log(`These two rectangles overlap by: ${verticalOverlap.length} squares`)
// Then just do the same for horizontal
}
rect1 = {...rect1, getEndpoints(rect1)}
rect2 = {...rect2, getEndpoints(rect2)}
getOverlap(rect1, rect2);

p5.js Flood Fill (bucket tool) works slowly and wierd

So I wrote a flood fill function that works like a paint-app bucket tool: you click inside a closed shape and it'll fill with a color.
I have two problems with it:
performance - let's say my canvas is 600*600 (370,000 pixels) and I draw a big circle in it that for example has about 100K pixels in it, it can take about 40(!!!) seconds to fill this circle! thats insane!
A sqaure of exactly 10,000 pixels takes 0.4-0.5 seconds on average, but (I guess) since the sizes of the arrays used the program are growing so much, a sqaure 10 times the size takes about 100 times the length to fill.
there's something wierd about the filling. I'm not really sure how it happens but it's always leaving a few un-filled pixels. Not much at all, but it's really wierd.
My flood fill function uses 4 helper-functions: get and set pixel color, checking if it's a color to fill, and checking if that's a pixel that has been checked before.
Here are all the functions:
getPixelColor = (x, y) => {
let pixelColor = [];
for (let i = 0; i < pixDens; ++i) {
for (let j = 0; j < pixDens; ++j) {
index = 4 * ((y * pixDens + j) * width * pixDens + (x * pixDens + i));
pixelColor[0] = pixels[index];
pixelColor[1] = pixels[index + 1];
pixelColor[2] = pixels[index + 2];
pixelColor[3] = pixels[index + 3];
}
}
return pixelColor;
};
setPixelColor = (x, y, currentColor) => { //Remember to loadPixels() before using this function, and to updatePixels() after.
for (let i = 0; i < pixDens; ++i) {
for (let j = 0; j < pixDens; ++j) {
index = 4 * ((y * pixDens + j) * width * pixDens + (x * pixDens + i));
pixels[index] = currentColor[0];
pixels[index + 1] = currentColor[1];
pixels[index + 2] = currentColor[2];
pixels[index + 3] = currentColor[3];
}
}
}
isDuplicate = (posHistory, vector) => {
for (let i = 0; i < posHistory.length; ++i) {
if (posHistory[i].x === vector.x && posHistory[i].y === vector.y) {
return true;
}
}
return false;
}
compareColors = (firstColor, secondColor) => {
for (let i = 0; i < firstColor.length; ++i) {
if (firstColor[i] !== secondColor[i]) {
return false;
}
}
return true;
}
floodFill = () => {
loadPixels();
let x = floor(mouseX);
let y = floor(mouseY);
let startingColor = getPixelColor(x, y);
if (compareColors(startingColor, currentColor)) {
return false;
}
let pos = [];
pos.push(createVector(x, y));
let posHistory = [];
posHistory.push(createVector(x, y));
while (pos.length > 0) {
x = pos[0].x;
y = pos[0].y;
pos.shift();
if (x <= width && x >= 0 && y <= height && y >= 0) {
setPixelColor(x, y, currentColor);
let xMinus = createVector(x - 1, y);
if (!isDuplicate(posHistory, xMinus) && compareColors(getPixelColor(xMinus.x, xMinus.y), startingColor)) {
pos.push(xMinus);
posHistory.push(xMinus);
}
let xPlus = createVector(x + 1, y);
if (!isDuplicate(posHistory, xPlus) && compareColors(getPixelColor(xPlus.x, xPlus.y), startingColor)) {
pos.push(xPlus);
posHistory.push(xPlus);
}
let yMinus = createVector(x, y - 1);
if (!isDuplicate(posHistory, yMinus) && compareColors(getPixelColor(yMinus.x, yMinus.y), startingColor)) {
pos.push(yMinus);
posHistory.push(yMinus);
}
let yPlus = createVector(x, y + 1);
if (!isDuplicate(posHistory, yPlus) && compareColors(getPixelColor(yPlus.x, yPlus.y), startingColor)) {
pos.push(yPlus);
posHistory.push(yPlus);
}
}
}
updatePixels();
}
I would really apprciate it if someone could help me solve the problems with the functions.
Thank you very much!!
EDIT: So I updated my flood fill function itself and removed an array of colors that I never used. this array was pretty large and a few push() and a shift() methods called on it on pretty much every run.
UNFORTUNATLY, the execution time is 99.9% the same for small shapes (for example, a fill of 10,000 takes the same 0.5 seconds, but large fills, like 100,000 pixels now takes about 30 seconds and not 40, so that's a step in the right direction.
I guess that RAM usage is down as well since it was a pretty large array but I didn't measured it.
The wierd problem where it leaves un-filled pixels behind is still here as well.
A little suggestion:
You don't actually have to use the posHistory array to determine whether to set color. If the current pixel has the same color as startingColor then set color, otherwise don't set. This would have the same effect.
The posHistory array would grow larger and larger during execution. As a result, a lot of work has to be done just to determine whether to fill a single pixel. I think this might be the reason behind your code running slowly.
As for the "weird thing":
This also happened to me before. I think that's because the unfilled pixels do not have the same color as startingColor. Say you draw a black shape on a white background, you would expect to see some gray pixels (close to white) between the black and white parts somewhere. These pixels play the role of smoothing the shape.

gRaphael linechart draws values beyond axis

When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.
Does anyone know:
1) Why this occurs,
2) How to prevent it, &/or
3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).
my code:
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000, 1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r2.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines2.symbols.attr({ r: 3 });
I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)
Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/
Secondary example fiddle (4th example on page frequently shows both axis errors):
http://jsfiddle.net/kcar/saBnT/
root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.
Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.
code of that function from g.raphael.js
snapEnds: function(from, to, steps) {
var f = from,
t = to;
if (f == t) {
return {from: f, to: t, power: 0};
}
function round(a) {
return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
}
var d = (t - f) / steps,
r = ~~(d),
R = r,
i = 0;
if (r) {
while (R) {
i--;
R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
}
i ++;
} else {
if(d == 0 || !isFinite(d)) {
i = 1;
} else {
while (!r) {
i = i || 1;
r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
i++;
}
}
i && i--;
}
t = round(to * Math.pow(10, i)) / Math.pow(10, i);
if (t < to) {
t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
}
f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
return { from: f, to: t, power: i };
},
removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.
code of that function from g.raphael.js now:
snapEnds: function(from, to, steps) {
return {from: from, to: to, power: 0};
},
Hi if you comment this:
if (valuesy[i].length > width - 2 * gutter) {
valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
len = width - 2 * gutter;
}
if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
}
in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.
Upgrading from v0.50 to v0.51 fixed the issue for me.
Still not sure why it occurs, adding in a transparent set was not a desirable option.
The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.
If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers
//assuming datapoints is the Raphael Set for the datapoints, axes is the
//Raphael Set for the axis, and datalines is the Raphael Set for the
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x);
//rounding up to integer to simplify, and the extra boost from y-axis doesn't
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
datapoints.transform('t' +xGapLeft +',0');
datalines.transform('t' +xGapLeft +',0');
xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }
//in this case the initial point is right of y-axis, the end point is right of
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis
datapoints.transform('t' +xGapRight +',0');
datalines.transform('t' +xGapRight +',0');
xGap = xGapRight;
}
rehookHoverOverEvent(xGap); //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original
//datapoints instead of the new location, at least they were in my case.

Categories