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

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);

Related

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.

Fabric js: when button is clicked center canvas and zoom to object

I'm trying to center the viewport on an object and zoom in/out when i click a button. I want to have an animation. To do this I'm using setInterval and moving the viewport in increments like so:
const objectCenterCoordenates = {
x: Math.round(rect1.left + rect1.width / 2),
y: Math.round(rect1.top + rect1.height / 2)
};
const centeredCanvasCoordenates = {
x: objectCenterCoordenates.x - canvas.width / 2,
y: objectCenterCoordenates.y - canvas.height / 2
};
let currentPoint = {
x: Math.round(canvas.viewportTransform[4]) * -1,
y: Math.round(canvas.viewportTransform[5]) * -1
};
console.log("Start animation");
let animation = setInterval(() => {
console.log("New frame");
if (canvas.getZoom() !== 2) {
let roundedZoom = Math.round(canvas.getZoom() * 100) / 100;
let zoomStep = roundedZoom > 2 ? -0.01 : 0.01;
let newZoom = roundedZoom + zoomStep;
canvas.zoomToPoint(
new fabric.Point(currentPoint.x, currentPoint.y),
newZoom
);
}
let step = 5;
let vpCenter = {
x: Math.round(canvas.getVpCenter().x),
y: Math.round(canvas.getVpCenter().y)
};
if (
vpCenter.x === objectCenterCoordenates.x &&
vpCenter.y === objectCenterCoordenates.y
) {
console.log("Animation Finish");
clearInterval(animation);
}
let xDif = Math.abs(vpCenter.x - objectCenterCoordenates.x);
let yDif = Math.abs(vpCenter.y - objectCenterCoordenates.y);
let stepX =
Math.round(vpCenter.x) > Math.round(objectCenterCoordenates.x)
? -step
: step;
if (Math.abs(xDif) < step)
stepX =
Math.round(vpCenter.x) > Math.round(objectCenterCoordenates.x)
? -xDif
: xDif;
let stepY =
Math.round(vpCenter.y) > Math.round(objectCenterCoordenates.y)
? -step
: step;
if (Math.abs(yDif) < step)
stepY =
Math.round(vpCenter.y) > Math.round(objectCenterCoordenates.y)
? -yDif
: yDif;
currentPoint = {
x: currentPoint.x + stepX,
y: currentPoint.y + stepY
};
canvas.absolutePan(new fabric.Point(currentPoint.x, currentPoint.y));
}, 4);
But sometimes, I haven't been able to determine why, it gets stuck in a loop moving a few pixels one way then going back the other. And the zoom is well implemented, since it's possible to get to the object before it finished zooming in/out.
Here is a codepen with my code: https://codepen.io/nilsilva/pen/vYpPrgq
I would appreciate any advice on making my algorithm better.
Looks like you have an exact match requirement for your clearInterval ...
if (
vpCenter.x === objectCenterCoordenates.x &&
vpCenter.y === objectCenterCoordenates.y
) {
console.log("Animation Finish");
clearInterval(animation);
}
The case that you describe stuck in a loop moving one way then going back the other is because the exact match it's never done, could be because the step you are taking or it could be a rounding issue
You can use Math.hypot to check if the two points are close enough, read more here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
The condition will look like:
if (Math.hypot(
vpCenter.x - objectCenterCoordenates.x,
vpCenter.y - objectCenterCoordenates.y
) < step) {
console.log("Animation Finish");
clearInterval(animation);
}

Optimise object overlapping detection (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))

Can I make a half-bezier from full bezier?

Take a typical cubic bezier curve drawn in JavaScript (this example I googled...)
http://jsfiddle.net/atsanche/K38kM/
Specifically, these two lines:
context.moveTo(188, 130);
context.bezierCurveTo(170, 10, 350, 10, 388, 170);
We have a cubic bezier which starts at 188, 130, ends at 388, 170, and has controls points a:170, 10 and b:350, 10
My question is would it be possible to mathematically adjust the end point and control points to make another curve which is only a segment of the original curve?
The ideal result would be able to able to take a percentage slice of the bezier from the beginning, where 0.5 would draw only half of the bezier, 0.75 would draw most of the bezier (and so on)
I've already gotten working a few implementations of De Castelau which allow me to trace the contour of the bezier between [0...1], but this doesn't provide a way to mathematically recalculate the end and control points of the bezier to make a sub-bezier...
Thanks in advance
De Casteljau is indeed the algorithm to go. For a cubic Bezier curve defined by 4 control points P0, P1, P2 and P3, the control points of the sub-Bezier curve (0, u) are P0, Q0, R0 and S0 and the control points of the sub-Bezier curve (u, 1) are S0, R1, Q2 and P3, where
Q0 = (1-u)*P0 + u*P1
Q1 = (1-u)*P1 + u*P2
Q2 = (1-u)*P2 + u*P3
R0 = (1-u)*Q0 + u*Q1
R1 = (1-u)*Q1 + u*Q2
S0 = (1-u)*R0 + u*R1
Please note that if you want to "extract" a segment (u1, u2) from the original Bezier curve, you will have to apply De Casteljau twice. The first time will split the input Bezier curve C(t) into C1(t) and C2(t) at parameter u1 and the 2nd time you will have to split the curve C2(t) at an adjusted parameter u2* = (u2-u1)/(1-u1).
This is how to do it. You can get the left half or right half with this functin. This function is take thanks to mark from here: https://stackoverflow.com/a/23452618/1828637
I have it modified so it can be fit to a unit cell so we can use it for cubic-bezier in css transitions.
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
Thats the function and now to use it with your parameters.
var myBezier = {
xs: [188, 170, 350, 388],
ys: [130, 10, 10, 170]
};
var splitRes = splitCubicBezier({
z: .5, //percent
x: myBezier.xs,
y: myBezier.ys,
fitUnitSquare: false
});
This gives you
({
left: [188, 130, 179, 70, 219.5, 40, 267, 45],
right: [267, 45, 314.5, 50, 369, 90, 388, 170]
})
fiddle proving its half, i overlaid it over your original:
http://jsfiddle.net/K38kM/8/
Yes it is! Have a look at the bezier section here
http://en.m.wikipedia.org/wiki/De_Casteljau's_algorithm
It is not that difficult all in all.

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