I need to evenly distribute a bunch of axis-aligned sliding rectangles constrained by a maximum width/height and by some horizontal/vertical coordinates depending from the position of the sliding shapes itself. Rectangles are constrained in one direction, can slide along the other axis, may not overlap and not step over as well.
This question is based on: How to implement a constraint solver for 2-D geometry? and Spektre's well accepted proposal for a force-driven constraint solver.
The whole structure is build as usual as a graph where the rectangles represent the nodes.
Now, i need to check the size of each rectangle to get the correct force calculation and to avoid overlap, but i have some trouble to understand how a force field could be applied to a 2-D shape, and how the distance between two rectangles shall be calculated. Maybe the vertices or the sides?
Relevant code is in the function Solver.solve() below, where s.Z represent respectively the height of a shape for the horizontal ones, and the width for the vertical ones:
for(var i=0, l=sliders.length; i<l; i++) {
var si = sliders[i];
for(var j=i+1, k=sliders.length; j<k; j++) {
var sj = sliders[j];
if(si._horizontal == sj._horizontal) {
// longer side interaction
if(si._horizontal == 1) {
a0 = si.X + si.a; a1 = sj.X + sj.a;
b0 = si.X + si.b; b1 = sj.X + sj.b;
x0 = si.Y; x1 = sj.Y;
} else {
a0 = si.Y + si.a; a1 = sj.Y + sj.a;
b0 = si.Y + si.b; b1 = sj.Y + sj.b;
x0 = si.X; x1 = sj.X;
}
if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))) {
x0 = x1 - x0;
if((si.ia >= 0) && (x0 < 0.0) && ((fabs(si.x0) < si.Z) || (fabs(si.x0) > fabs(x0)))) si.x0 = -x0;
if((si.ia >= 0) && (x0 > 0.0) && ((fabs(si.x1) < si.Z) || (fabs(si.x1) > fabs(x0)))) si.x1 = -x0;
if((sj.ia >= 0) && (x0 < 0.0) && ((fabs(sj.x0) < sj.Z) || (fabs(sj.x0) > fabs(x0)))) sj.x0 = +x0;
if((sj.ia >= 0) && (x0 > 0.0) && ((fabs(sj.x1) < sj.Z) || (fabs(sj.x1) > fabs(x0)))) sj.x1 = +x0;
}
// shorter side interaction
if(si._horizontal == 1) {
a0 = si.Y - si.Z; a1 = sj.Y + sj.Z;
b0 = si.Y + si.Z; b1 = sj.Y + sj.Z;
x0 = si.X; x1 = sj.X;
} else {
a0 = si.X - si.Z; a1 = sj.X + sj.Z;
b0 = si.X + si.Z; b1 = sj.X + sj.Z;
x0 = si.Y; x1 = sj.Y;
}
if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))) {
if(x0 < x1) {
x0 += si.b; x1 += sj.a;
} else{
x0 += si.a; x1 += sj.b;
}
x0 = x1 - x0;
if(si.ia >= 0) {
var sa = this.sliders[si.ia];
if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = -x0;
if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = -x0;
}
if(sj.ia >= 0) {
var sa = sliders[sj.ia];
if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = +x0;
if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = +x0;
}
}
}
}
}
// set x0 as 1D vector to closest perpendicular neighbour before and x1 after
for(var i=0, l=sliders.length; i<l; i++) {
var si = sliders[i];
for(var j=i+1, k=sliders.length; j<k; j++) {
var sj = sliders[j];
if(si._horizontal != sj._horizontal) {
// skip ignored sliders for this
var ignore = false;
for(var n=0, m=si.ic.length; n<m; n++) {
if(si.ic[n] == j) {
ignore = true;
break;
}
}
if(ignore === true) continue;
if(si._horizontal == 1) {
a0 = si.X + si.a; a1 = sj.X - sj.Z;
b0 = si.X + si.b; b1 = sj.X + sj.Z;
x0 = si.Y;
} else {
a0 = si.Y + si.a; a1 = sj.Y - sj.Z;
b0 = si.Y + si.b; b1 = sj.Y + sj.Z;
x0 = si.X;
}
if(((a0 <= b1) && (b0 >= a1)) || ((a1 <= b0) && (b1 >= a0))){
if(si._horizontal == 1) {
a1 = sj.Y + sj.a;
b1 = sj.Y + sj.b;
} else {
a1 = sj.X + sj.a;
b1 = sj.X + sj.b;
}
a1 -= x0; b1 -= x0;
if(fabs(a1) < fabs(b1)) x0 = -a1; else x0 = -b1;
if((si.ia >= 0) && (x0 < 0.0) && ((fabs(si.x0) < si.Z) || (fabs(si.x0) > fabs(x0)))) si.x0 = +x0;
if((si.ia >= 0) && (x0 > 0.0) && ((fabs(si.x1) < si.Z) || (fabs(si.x1) > fabs(x0)))) si.x1 = +x0;
if(sj.ia < 0) continue;
var sa = sliders[sj.ia];
if((sa.ia >= 0) && (x0 < 0.0) && ((fabs(sa.x0) < sa.Z) || (fabs(sa.x0) > fabs(x0)))) sa.x0 = -x0;
if((sa.ia >= 0) && (x0 > 0.0) && ((fabs(sa.x1) < sa.Z) || (fabs(sa.x1) > fabs(x0)))) sa.x1 = -x0;
}
}
}
}
How shall be the force calculation for rectangular shapes, to get from the force field an evenly distribution, i.e. such as the distance between rectangles will be the largest possible? Think like the rectangles are really hot and must be spaced at most, with respect to their custom x/y constraints.
Any help will be greatly appreciated.
EDIT:
Sample: https://plnkr.co/edit/3xGmAKsly2qCGMp3fPrJ?p=preview
If I get this question right OP asks for set of rules for driving the sliders so the final simulation state is leading to valid solution.
So here is my approach it is really recapitulation of my solver code from linked answer in OP but it would not fit in there as I already hit the 30 KByte limit and I feel it needs a bit more explaining then just commented code so here it is:
Forces
To ensure equal spacing as much as possible you need to change the rules a bit (apart from real physics) so you are accounting only the nearest obstacle to driving Force instead of all of them as in real world. Also the force is affected only by distance and not weighted also by area of contact/overlap as most physical forces do (electrostatic included).
So during iteration of i-th slider (Yellow) find the distances to closest obstacle in all 4 directions (Red):
And compute the driving force which has to be scaled with distance. Does not really matter if linearly or not but for evenly spaced obstacles from left/right or up/down should be the resultant force zero. Scaling changes mostly the dynamics behavior. The final outcome is affected by it only in states where constrains blocks the movement to achieve even spacing. So you can use for example any of these:
F = c * (d1-d0)
F = c * (d1^2 - d0^2)
F = c * (1/d1^2 - 1/d0^2)
Where c is some magnitude coefficient and d0,d1 are the 2 closest obstacle distances in the same axis.
Now from this you will obtain 2 forces (one for each axis)
Fx - horizontal axis force
Fy - vertical axis force
In a nutshell that is it. But there is a problem with the constrains. For example selected slider (Yellow) is vertical. That means it can move only in x axis. So you put the Fx driving force to it. The Fy force should drive its parent slider (Blue) which is horizontal and can move in y axis (if not fixed of coarse).
This introduce a problem because that slider can also have its own Fy so you should always select only the strongest force from each side. That means you need to remember both distances/forces from each side and select always the smallest distance or |highest| force from each side.
That is where the x0,x1 variables come from they hold the min distance in movement able axis to nearest obstacles (from child included) and only after computation of all sliders are converted to Fx,Fy driving forces.
To detect neighbors and collisions I recognize 3 types of interaction
perpendicular which is between horizontal and vertical slider (bottom interaction on image)
wide parallel which is between two sliders of the same type contacting with its longer sides (left,right interaction on image)
short parallel which is between two sliders of the same type contacting with its shorter sides (top interaction on image)
Limits and coefficients
I also introduced some speed limits and coefficients (not just for dumping to avoid oscillations). The speed and acceleration limits affects the solution time and stability. There is also another reason to use them (I do not do) and that is to preserve the order of sliders so they do not skip through each other. That can be done by simply limiting the top speed so per single iteration any slider will not move more then the half of thickness of sliders. So in case of collision the collision routine will kick in.
I am skipping that in my code because my config is set so the order of sliders is consistent with its index number. So if I detect that any slider is on left side of some tested slider while its index is higher it means it has skipped and I handle it by restoring last position instead... This is cheap hack and will not work in complex sets where are many child sliders in chain not just one.
Feedback
Due to constraints sometimes your driving force can get lost (traveling to fixed or stuck parent slider) in case this behavior corrupts the result you should propagate opposite force the other way around too to the neighbor it caused it. For simple constructs is this not necessary as the driving force it already contain but for more complicated children chains It could pose a possible threat (but that is my wild thinking and I may be wrong in this).
In nature this is provided by integrating force from all of the neighboar objects instead of just the closest ones but that would lead to non equal spacing I think.
Related
Hi,
I'm developing a visual like a scatter plot using D3.js it has around 20k points without labels. I want to show the labels for the filtered data. I modified a function to avoid labels overlapping. It works but if I have a large number of points after applying the filter it cause crush the browser !!
Any ideas to improve the algorithm ? or to use the force function in D3v3 to do the job ?
function arrangeLabels(svg) {
var move = 1;
while(move > 0) {
move = 0;
svg.selectAll(".dotB")
.each(function() {
var that = this,
a = this.getBoundingClientRect();
svg.selectAll("text.dotB")
.each(function() {
if (this != that) {
var b = this.getBoundingClientRect();
if ((Math.abs(a.left - b.left) * 2 < (a.width + b.width)) &&
(Math.abs(a.top - b.top) * 2 < (a.height + b.height))) {
var dy = (a.bottom + b.height)+2,
move += Math.abs(dy);
d3.select(this).attr("y", dy);
a = this.getBoundingClientRect();
}
}
});
})
}
I found this method to solve the overlapping issue using D3v4 https://walkingtree.tech/d3-quadrant-chart-collision-in-angular2-application/ any idea how to do the same in D3v3 ?!
So the problem is with the if logic:
if (
(Math.abs(a.left - b.left) * 2 < (a.width + b.width)) &&
(Math.abs(a.top - b.top) < a.height + b.height)
) {
var dy = (a.bottom + b.height)+2,
move += Math.abs(dy);
d3.select(this).attr("y", dy);
a = this.getBoundingClientRect();
}
I nailed a collision detection system, but now i'm trying to make a system that makes it so that, when the function is called, the items in the parameters aren't able to touch each other. I'm fairly new to JavaScript and its the first language ive really tried to learn. The way my rectangles are being drawn is so that x and y are in the middle of the rect, rather than in the top left corner of it. The system i've built technically works, but only if its a perfect square, for some reason rectangles are buggy and that i cant figure out. Even when it is a perfect square though, it seems clunky and really bad compared to what i'm used to, which is code.org's item1.collide(item2); which works perfectly and exactly how I want, but i cant find the code behind that. I am using p5.js, by the way.
Here is how i'm drawing my rectangles:
rect(this.x-this.width/2,this.y-this.height/2,this.width,this.height);
And here is the blockCollision function I currently have:
function blockCollision(a,b){
if(a.x+a.width/2 > b.x-b.width/2 &&
a.x-a.width/2 < b.x+b.width/2 &&
a.y-a.height/2 < b.y+b.height/2 &&
a.y+a.height/2 > b.y-b.height/2) {
if(a.x<b.x-b.width/2) a.x=b.x-b.width/2-a.width/2;
if(a.x>b.x+b.width/2) a.x=b.x+b.width/2+a.width/2;
if(a.y<b.x-b.height/2) a.y=b.x-b.height/2-a.height/2;
if(a.y>b.x+b.height/2) a.y=b.x+b.height/2+a.height/2;
}
}
Also, here is the entire code download if It helps: https://drive.google.com/open?id=0B-F5CHOIQvvGVlR3Njd1M1NLS1E
I presume by "block collision", you mean that you would like to move one of the blocks in the "opposite" direction of the collision so that the collision does not happen.
What you need to do is essentially determine the direction of the collision (top/bottom/left/right), and move the offending block away:
function blockCollision(a, b) {
// Assuming (0, 0) is the top left corner
const aTop = a.y - a.height / 2;
const aBottom = a.y + a.height / 2;
const aLeft = a.x - a.width / 2;
const aRight = a.x + a.width / 2;
const bTop = b.y - b.height / 2;
const bBottom = b.y + b.height / 2;
const bLeft = b.x - b.width / 2;
const bRight = b.x + b.width / 2;
const collisions = [];
if (aTop <= bTop && aBottom >= bTop) {
// a is above B, potential collision
if (aLeft <= bRight && bLeft <= aRight) {
// Prevent collision, push a to the top
a.y = bTop - (a.height / 2) - 1;
}
}
if (aBottom >= bBottom && aTop <= bBottom) {
// a is below B, potential collision
if (aLeft <= bRight && bLeft <= aRight) {
// Prevent collision
a.y = bBottom + (a.height / 2) + 1;
}
}
if (aLeft <= bLeft && aRight >= bLeft) {
// a is left of B, potential collision
if (aTop <= bBottom && bTop <= aBottom) {
// Prevent collision, push a to the left
a.x = bLeft - (a.width / 2) - 1;
}
}
if (aRight >= bRight && aLeft <= bRight) {
// a is right of B, potential collision
if (aTop <= bBottom && bTop <= aBottom) {
// Prevent collision, push a to the right
a.x = bRight + (a.width / 2) + 1;
}
}
}
See codepen example.
I need to detect wether two objects collide / overlap with each other,
for achieving this purpose I stumbled upon the collision algorithm used in the "run pixie run" game, that didn't work, so I passed to this other function I found on the pixijs forum ( code follows below ), but even this works only in some cases.
The objects involved in the hit test are two DisplayObjectContainer containing a Sprite and a Graphics element (namely a rectangle that used for showing the boundingBox of the sprite).
The sprite has the anchor point set to 0.5 ( for that reason the x/y values in the function are inited like this )
var hitTest = function(s2, s1)
{
var x1 = s1.position.x - (s1.width/2),
y1 = s1.position.y - (s1.height/2),
w1 = s1.width,
h1 = s1.height,
x2 = s2.position.x - ( s2.width / 2 ),
y2 = s2.position.y - ( s2.height / 2 ),
w2 = s2.width,
h2 = s2.height;
if (x1 + w1 > x2)
if (x1 < x2 + w2)
if (y1 + h1 > y2)
if (y1 < y2 + h2)
return true;
return false;
};
I also read that it might be possible to use the box2d engine to perform such a task, but I find this solution a little bit overwhelming.
I was looking for a simple as convenient way to do so.
In the end I came up with this solution, that I found on mdn and changed in order to fit my scenario.
var isColliding = function(el) {
el.children[0].position, el.children[1].position, el.position);
rect1 = {
x:el.position.x-(el.children[0].width/2),
y:el.position.y-(el.children[0].height/2),
w:el.children[0].width,
h:el.children[0].height
}
for(i=0; i<stage.children.length;i++)
{
if(stage.children[i] != el) {
el2 = stage.children[i]
rect2 = {
x:el2.position.x-(el2.children[0].width/2),
y:el2.position.y-(el2.children[0].height/2),
w:el2.children[0].width,
h:el2.children[0].height
}
if (rect1.x < rect2.x + rect2.w &&
rect1.x + rect1.w > rect2.x &&
rect1.y < rect2.y + rect2.h &&
rect1.h + rect1.y > rect2.y) {
return true;
}
}
}
return false;
So I have 2 functions that I was hoping would work together to bounce an object around a div. I'm using a graphics library, so there will be some unfamiliar pieces of code below. I think you'll still understand the gist of what I'm trying to do.
function homepage_screensaver()
{
/*
Create Raphael object in the space of the div with id "homeandidiv"
*/
var pappos = $("#homeanidiv").position();
var papx = pappos.left;
var papy = pappos.top;
var papheight = $("#homeanidiv").height();
var papwidth = $("#homeanidiv").width();
var paper = Raphael(papx, papy, papwidth, papheight);
/*
paper: space in which the circle will be bound
*/
var circx = Math.floor(Math.random() * Number.MAX_VALUE) % papwidth;
var circy = Math.floor(Math.random() * Number.MAX_VALUE) % papheight;
/*
circx, circy: initial positions of circle on the paper
*/
var mycirc = paper.circle(circx, circy, 10);
mycirc.attr("fill","#F9C624");
var theta = Math.floor(Math.random() * Number.MAX_VALUE) % 4 + 1;
/*
theta = 1 <---> object moving at a 45-degree angle
theta = 2 <---> object moving at a 135-degree angle
theta = 3 <---> object moving at a 225-degree angle
theta = 4 <---> object moving at a 315 degree angle
*/
var circwrapper = new Array(mycirc, theta);
window.setInterval(function() { move_obj(circwrapper, papwidth, papheight);}, 100);
}
function move_obj(obwrap, backwidth, backheight)
{
var ob = obwrap[0]; // object
var th = obwrap[1]; // theta, the current direction of the object
var BB = ob.getBBox(); // bounding box for object
var dx = 0;
var dy = 0;
if (BB.x >= backwidth && (th == 1 || th == 2))
dx = -1;
else if (BB.x <= 0 && (th == 3 || th == 4))
dx = 1;
if (BB.y >= backheight && (th == 2 || th == 3))
dy = -1;
else if (BB.y <= 0 && (th == 1 || th == 4))
dy = 1;
ob.transform("T " + dx + ", " + dy);
if (dx == 1 && dy == -1)
th = 1;
else if (dx == 1 && dy == 1)
th = 2;
else if (dx == -1 && dy == 1)
th = 3;
else // (dx == -1 && dy == -1)
th = 4;
obwrap[0] = ob;
obwrap[1] = th;
}
Here's the problem that I've realized after testing my page: my function move_obj(...) is not actually affecting the first parameter I'm passing to it. You can see at the end of my function that I have
obwrap[0] = ob;
obwrap[1] = th;
indicating that I'm trying to actually modify values of the array that is passed in as the first parameter.
Is there any "quick fix" to my problem? I would prefer not to go back and try to make things global variables.
Just so you know, I have researched the issue of passing by reference in JS and here it says that arrays are passed by reference: http://orizens.com/wp/topics/javascript-arrays-passing-by-reference-or-by-value/. So I don't see what's going wrong here.
You have to do the reassignment after the "move" function returns.
window.setInterval(function() {
var wrapper = [mycirc, theta];
move_obj(wrapper, papwidth, papheight);
mycirc = wrapper[0];
theta = wrapper[1];
}, 100);
Assigning new values to the array works but it only affects the array. When you build the array, you're making copies of the values of the two variables. There's no subsequent implicit relationship between the array slots and the variables, so changes to the array have no effect on the values of the independent variables.
Essentially, what I'm doing is placing a bunch of random width/height rects onto a grid (near the center of it), then pushing them all away from each other until none of them overlap. I have another version where I check for collisions before I place them on the grid, but that's not what I'm going for in this build.
I'm wondering if someone can explain a better way to go about this?
What I've tried so far is something similar to:
let r1/r2 = rect1/rect2
do {
var ox = Math.max(0, Math.min(r1.x + r1.w, r2.x + r2.w) - Math.max(r1.x, r2.x)),
oy = Math.max(0, Math.min(r1.y + r1.h, r2.y + r2.h) - Math.max(r1.y, r2.y)),
dx = r2.x - r1.x,
dy = r2.y - r1.y;
if (ox > 0 && oy > 0) {
if (ox >= oy) {
if (r1.x >= r2.x && Math.random() > .1) {
r1.x += ox;
spaced = true;
continue;
} else {
r1.x -= ox;
spaced = true;
continue;
}
} else {
if (r1.y >= r2.y && Math.random() > .1) {
r1.y += oy;
spaced = true;
continue;
} else {
r1.y -= oy;
spaced = true;
continue;
}
}
}
} while ( /* stuff */ )
the random is only there because I will run into times when a certain rect gets pushed back and forth and never gets free and causes an infinite loop. This way is horribly inefficient however.
I believe what your trying to accomplish is known as a packing problem http://en.wikipedia.org/wiki/Packing_problem. If you just search stack overflow for "2d bin packing" you should be able to find all you need to roll a much more efficient algorithm.