algorithm to randomly & efficiently place 100 circles without any overlap? - javascript

I am trying to write a script to place 100 circles of varying sizes onto a stage. I've outlined the concise requirements below.
Given the following:
var stage; // contains a "width" and "height" property.
var circle; // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances
requirements:
write a function to place 100 circles of varying radius onto the stage.
placements must be random but evenly distributed (no clumping).
placement must be performant - this will be executing on a mobile web browser.
circles must not intersect/overlap other circles.
circle.x >= 0 must be true.
circle.y >= 0 && circle.y <= stage.height must be true.
circles may have any of the following radius sizes (assigned at creation):
150
120
90
80
65
My current attempt is a brute-force method, which does not operate efficiently. If I attempt to insert any more than ~10 circles, the browser hangs. Below is my current implementation, which I am completely OK with throwing away in favor of a more performant / better one.
Here is a live demo (NOTE: there is no actual drawing code, just the logic, but it will still lock up the browser so be warned!!) http://jsbin.com/muhiziduxu/2/edit?js,console
function adjustForOverlap (circleArray) {
// a reference to the circle that is invoking this function.
var _this = this;
// remove this circle from the array we are iterating over.
var arr = circleArray.filter(function (circle){
return circle.id !== _this.id;
});
// while repeat == true, the circle may be overlapping something.
var repeat = true;
while(repeat) {
var hasOverlap = false;
for (var i=0; i<arr.length; i++) {
var other = arr[i];
var dx = _self.x - other.x;
var dy = _self.y - other.y;
var rr = _self.radius + other.radius;
if (dx * dx + dy * dy < rr * rr) {
// if here, then an overlap was detected.
hit = true;
break;
}
}
// if hit is false, the circle didn't overlap anything, so break.
if (hit === false) {
repeat = false;
break;
} else {
// an overlap was detected, so randomize position.
_self.x = Math.random() * (stage.width*2);
_self.y = Math.random() * stage.height;
}
}
}

There are lots of efficient collision detection algorithms. Many of them work by dividing up the space into cells and maintaining a separate data structure with efficient lookup of other objects in the cell. The basic steps are:
Identify a random spot for your new circle
Determine which cells it's in
Look in each of those cells for a collision
If there's a collision, goto 1.
Else, add the new circle to each of the cells it overlaps.
You can use a simple square grid (i.e. a 2-d array) for the cell data structure, or something else like a quadtree. You can also in some cases get a bit of extra speed by trying a cheap-but-coarse collision check first (do the bounding boxes overlap), and if that returns true try the slightly more expensive and exact check.
Update
For quadtrees, check out d3-quadtree, which ought to give you a pretty good implementation, with examples.
For a (very quick, untested) 2-d array implementation:
function Grid(radius, width, height) {
// I'm not sure offhand how to find the optimum grid size.
// Let's use a radius as a starting point
this.gridX = Math.ceil(width / radius);
this.gridY = Math.ceil(height / radius);
// Determine cell size
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
// Create the grid structure
this.grid = [];
for (var i = 0; i < gridY; i++) {
// grid row
this.grid[i] = [];
for (var j = 0; j < gridX; j++) {
// Grid cell, holds refs to all circles
this.grid[i][j] = [];
}
}
}
Grid.prototype = {
// Return all cells the circle intersects. Each cell is an array
getCells: function(circle) {
var cells = [];
var grid = this.grid;
// For simplicity, just intersect the bounding boxes
var gridX1Index = Math.floor(
(circle.x - circle.radius) / this.cellWidth
);
var gridX2Index = Math.ceil(
(circle.x + circle.radius) / this.cellWidth
);
var gridY1Index = Math.floor(
(circle.y - circle.radius) / this.cellHeight
);
var gridY2Index = Math.ceil(
(circle.y + circle.radius) / this.cellHeight
);
for (var i = gridY1Index; i < gridY2Index; i++) {
for (var j = gridX1Index; j < gridX2Index; j++) {
// Add cell to list
cells.push(grid[i][j]);
}
}
return cells;
},
add: function(circle) {
this.getCells(circle).forEach(function(cell) {
cell.push(circle);
});
},
hasCollisions: function(circle) {
return this.getCells(circle).some(function(cell) {
return cell.some(function(other) {
return this.collides(circle, other);
}, this);
}, this);
},
collides: function (circle, other) {
if (circle === other) {
return false;
}
var dx = circle.x - other.x;
var dy = circle.y - other.y;
var rr = circle.radius + other.radius;
return (dx * dx + dy * dy < rr * rr);
}
};
var g = new Grid(150, 1000, 800);
g.add({x: 100, y: 100, radius: 50});
g.hasCollisions({x: 100, y:80, radius: 100});
Here's a fully-functional example: http://jsbin.com/cojoxoxufu/1/edit?js,output
Note that this only shows 30 circles. It looks like the problem is often unsolvable with your current radii, width, and height. This is set up to look for up to 500 locations for each circle before giving up and accepting a collision.

Related

Draw Map in Browser out of 2 Dimensional Array of Distances

I'm receiving all distances between a random number of points in a 2 dimensional coordinate system.
How can I visualize this as coordinates on a map in my browser?
In case there are many solutions I just want to see the first possible one that my algorithm can come up with.
So here's an extremely easy example:
PointCount = 3
Distances:
0-1 = 2
0-2 = 4
1-2 = 2
Does anyone know an easy way (existing solution/framework maybe) to do it using whatever is out there to make it easier to implement?
I was thinking maybe using the html canvas element for drawing, but I don't know how to create an algorithm that could come up with possible coordinates for those points.
The above example is simplified -
Real distance values could look like this:
(0) (1) (2) (3)
(0) 0 2344 3333 10000
(1) 0 3566 10333
(2) 0 12520
I'm not sure this is relevant for SO, but anyway...
The way to do this is quite simply to place the points one by one using the data:
Pick a random location for the first point (let's say it's 0,0).
The second point is on a circle with radius d(0,1) with the first point as its center, so you can pick any point on the circle. Let's pick (d(0,1),0).
The third point is at the intersection of a circle with radius d(0,2) and center point 1, and a circle with radius d(1,2) and center point 2. You will get either 0, 1, 2 or an infinity of solutions. If the data comes from real points, 0 shouldn't happen. 1 and infinity are edge cases, but you should still handle them. Pick any of the solutions.
The fourth point is at the intersection of 3 circles. Unless you're very unlucky (but you should account for it), there should be only one solution.
Continue like this until all points have been placed.
Note that this doesn't mean you'll get the exact locations of the original points: you can have any combination of a translation (the choice of your first point), rotation (the choice of your second point) and symmetry (the choice of your third point) making the difference.
A quick and dirty implementation (not handling quite a few cases, and tested very little):
function distance(p1, p2) {
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}
// adapted from https://stackoverflow.com/a/12221389/3527940
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* '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 = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0 * r0) - (a * a));
/* 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;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [
[xi, yi],
[xi_prime, yi_prime]
];
}
function generateData(nbPoints) {
var i, j, k;
var originalPoints = [];
for (i = 0; i < nbPoints; i++) {
originalPoints.push([Math.random() * 20000 - 10000, Math.random() * 20000 - 10000]);
}
var data = [];
var distances;
for (i = 0; i < nbPoints; i++) {
distances = [];
for (j = 0; j < i; j++) {
distances.push(distance(originalPoints[i], originalPoints[j]));
}
data.push(distances);
}
//console.log("original points", originalPoints);
//console.log("distance data", data);
return data;
}
function findPointsForDistances(data, threshold) {
var points = [];
var solutions;
var solutions1, solutions2;
var point;
var i, j, k;
if (!threshold)
threshold = 0.01;
// First point, arbitrarily set at 0,0
points.push([0, 0]);
// Second point, arbitrarily set at d(0,1),0
points.push([data[1][0], 0]);
// Third point, intersection of two circles, pick any solution
solutions = intersection(
points[0][0], points[0][1], data[2][0],
points[1][0], points[1][1], data[2][1]);
//console.log("possible solutions for point 3", solutions);
points.push(solutions[0]);
//console.log("solution for points 1, 2 and 3", points);
found = true;
// Subsequent points, intersections of n-1 circles, use first two to find 2 solutions,
// the 3rd to pick one of the two
// then use others to check it's valid
for (i = 3; i < data.length; i++) {
// distances to points 1 and 2 give two circles and two possible solutions
solutions = intersection(
points[0][0], points[0][1], data[i][0],
points[1][0], points[1][1], data[i][1]);
//console.log("possible solutions for point " + (i + 1), solutions);
// try to find which solution is compatible with distance to point 3
found = false;
for (j = 0; j < 2; j++) {
if (Math.abs(distance(solutions[j], points[2]) - data[i][2]) <= threshold) {
point = solutions[j];
found = true;
break;
}
}
if (!found) {
console.log("could not find solution for point " + (i + 1));
console.log("distance data", data);
console.log("solution for points 1, 2 and 3", points);
console.log("possible solutions for point " + (i + 1), solutions);
console.log("distances to point 3",
distance(solutions[0], points[2]),
distance(solutions[1], points[2]),
data[i][2]
);
break;
}
// We have found a solution, we need to check it's valid
for (j = 3; j < i; j++) {
if (Math.abs(distance(point, points[j]) - data[i][j]) > threshold) {
console.log("Could not verify solution", point, "for point " + (i + 1) + " against distance to point " + (j + 1));
found = false;
break;
}
}
if (!found) {
console.log("stopping");
break;
}
points.push(point);
}
if (found) {
//console.log("complete solution", points);
return points;
}
}
console.log(findPointsForDistances([
[],
[2344],
[3333, 3566],
[10000, 10333, 12520],
]));
console.log(findPointsForDistances([
[],
[2],
[4, 2],
]));
console.log(findPointsForDistances([
[],
[4000],
[5000, 3000],
[3000, 5000, 4000]
]));
console.log(findPointsForDistances([
[],
[2928],
[4938, 3437],
[10557, 10726, 13535]
]));
var nbPoints, i;
for (nbPoints = 4; nbPoints < 8; nbPoints++) {
for (i = 0; i < 10; i++) {
console.log(findPointsForDistances(generateData(nbPoints)));
}
}
Fiddle here: https://jsfiddle.net/jacquesc/82aqmpnb/15/
Minimum working example. Remember that in canvas coordinates, the y value is inverted but you could do something like:
y = canvasHeight - y
If you also have negative points then if would take a little bit of extra work. Also it may be helpful in that case to draw lines and tick marks to visualize the axis.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let scale = 10;
let radius = 10;
function point(x, y) {
ctx.fillRect(x*scale, y*scale, radius, radius);
}
// test
point(10, 15);
point(20, 8);
<html>
<body>
<canvas id="canvas" width=1000 height=1000></canvas>
</body>
</html>
There are plenty of libraries out there.
chartist.js is easy to use and responsive JavaS cript library. I used it last year for basic charts after trying many others but it was the only one that scaling easily in different screen sizes.
chartJS is another better looking library.
And you can use html5 canvas it's easy and fun but it will take time especially in scaling.
To scale and position, you should use the minimum and maximum values for x and y.
Good luck

Canvas animation with JavaScript. Random coordinates and speed at every initiation

Edited : Thanks to all for valuable time and effort. Finally I made this )) JSfiddle
I was just playing with canvas and made this. Fiddle link here.
... some code here ...
var cords = [];
for(var i = 50; i <= width; i += 100) {
for(var j = 50; j <= height; j += 100) {
cords.push({ cor: i+','+j});
}
}
console.log(cords);
var offset = 15,
speed = 0.01,
angle = 0.01;
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
setInterval(function() {
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
},5000);
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed;
requestAnimationFrame(render);
}
render();
Was wondering if -
Coordinates can be made random, now they are fixed as you can see. After 5000 mil, balls will show up in various random cords but even at their fullest they won't touch each other.
Every ball has same speed for changing size, I want that to be different too. Meaning, After 5000 mil, they show up with different animation speeds as well.
Also any suggestion on improving code and making it better/quicker/lighter is much appreciated. Thank you !
TL;DR - See it running here.
Making the coordinates random:
This requires you to add some random displacement to the x and y coordinates. So I added a random value to the coordinates. But then a displacement of less than 1 is not noticeable. So you'd need to magnify that random number by a multiplier. That's where the randomizationFactor comes in. I have set it to 100 since that is the value by which you shift the coordinates in each iteration. So that gives a truly random look to the animation.
Making Speed Random:
This one took me a while to figure out, but the ideal way is to push a value of speed into the array of coordinates. This let's you ensure that for the duration of animation, the speed will remain constant and that gives you a smoother feel. But again multiplying the radius r with a value between 0 and 1 reduces the speed significantly for some of the circles. So I have added a multiplier to 3 to compensate slightly for that.
Ideally I'd put a 2, as the average value of Math.random() is 0.5, so a multiplier of 2 would be adequate to compensate for that. But a little experimentation showed that the multiplier of 3 was much better. You can choose the value as per your preference.
Your logic of generating the coordinates changes as follows:
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
Your logic of enlarging the circles changes as follows:
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset * e1.speed * 3;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed ;
requestAnimationFrame(render);
}
Suggestion: Update the coordinates with color
I'd probably also update the location of circles every 5 seconds along with the colors. It's pretty simple to do as well. Here I've just created a function resetCoordinates that runs every 5 seconds along with the setBaseRgb function.
var cords = [];
function resetCoordinates() {
cords = [];
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
}
UPDATE I did some fixes in your code that can make your animation more dynamic. Totally rewritten sample.
(sorry for variable name changing, imo now better)
Built in Math.random not really random, and becomes obvious when you meet animations. Try to use this random-js lib.
var randEngine = Random.engines.mt19937().autoSeed();
var rand = function(from, to){
return Random.integer(from, to)(randEngine)
}
Internal base properties to each circle would be better(more dynamic).
var circles = [];
// better to save coords as object neither as string
for(var i = 50; i <= width; i += 100)
for(var j = 50; j <= height; j += 100)
circles.push({
coords: {x:i,y:j}
});
We can adjust animation with new bouncing property.
var offset = 15,
speed = 0.005,
angle = 0.01,
bouncing = 25;
This is how setBaseRgb function may look like
function setBaseRgb(el){
el.base = rand(-bouncing, bouncing);
el.speed = rand(5, 10) * speed;
el.angle = 0;
el.rgb = 'rgb('+rand(0, 255)+','+rand(0, 255)+','+rand(0, 255)+')';
}
All your animations had fixed setInterval timeout. Better with random timeout.
cords.forEach(function(el){
// random timeout for each circle
setInterval(setBaseRgb.bind(null,el), rand(3000, 5000));
})
You forgot to add your base to your circle position
function render() {
ctx.clearRect(0,0,width,height);
circles.forEach(function(el) {
ctx.fillStyle = el.rgb;
ctx.beginPath();
var r = bouncing + el.base + Math.abs(Math.sin(el.angle)) * offset;
var coords = el.coords;
ctx.arc(
coords.x + el.base,
coords.y + el.base,
r, 0, Math.PI * 2, false
);
ctx.fill();
el.angle += el.speed;
});
requestAnimationFrame(render);
}
render();
Effect 1 JSFiddle
Adding this
if(el.angle > 1)
el.angle=0;
Results bubling effect
Effect 2 JSFiddle
Playing with formulas results this
Effect 3 JSFiddle

create random rectangles with random colors without overlapping

How can i create something like this in QML using javascript?
Actually I know how to create rectangles in QML but want to do something like this. QML canvas can be of any size but whenever QML section is loaded multiple squares are generated with random sizes and colors without overlapping. When I'm trying to do this rectangles are generated in a list form.
I'm a web developer(ruby on rails oriented) but new to such javascript stuff. Any help will be appreciated.
As #ddriver already noticed, the simpliest decision is to loop through all children to find a room to a new rectangle.
Rectangle {
id: container
anchors.fill: parent
property var items: [];
Component {
id: rect
Rectangle {
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
border.width: 1
border.color: "#999"
width: 50
height: 50
}
}
Component.onCompleted: {
var cnt = 50;
for(var i = 0;i < cnt;i ++) {
for(var t = 0;t < 10;t ++) {
var _x = Math.round(Math.random() * (mainWindow.width - 200));
var _y = Math.round(Math.random() * (mainWindow.height - 200));
var _width = Math.round(50 + Math.random() * 150);
var _height = Math.round(50 + Math.random() * 150);
if(checkCoord(_x,_y,_width,_height)) {
var item = rect.createObject(container,{ x: _x, y: _y, width: _width, height: _height });
container.items.push(item);
break;
}
}
}
}
function checkCoord(_x,_y,_width,_height) {
if(container.items.length === 0)
return true;
for(var j = 0;j < container.items.length;j ++) {
var item = container.children[j];
if(!(_x > (item.x+item.width) || (_x+_width) < item.x || _y > (item.y+item.height) || (_y+_height) < item.y))
return false;
}
return true;
}
}
Yes, this is not so wise solution but it still can be improved.
If you want efficiency, it will come at the cost of complexity - you will have to use some space partition algorithm. Otherwise, you could just generate random values until you get enough that are not overlapping.
Checking whether two rectangles overlap is simple - if none of the corners of rectangle B is inside rectangle A, then they don't overlap. A corner/point is inside a rectangle if its x and y values are in the range of the rectangle's x and width and y and height respectively.
In JS Math.random() will give you a number between 0 and 1, so if you want to make a random value for example between 50 and 200, you can do that via Math.random() * 150 + 50.
Have an array, add the initial rectangle value to it, then generate new rectangle values, check if they overlap with those already in the array, if not - add them to the array as well. Once you get enough rectangle values, go ahead and create the actual rectangles. Since all your rectangles are squares, you can only go away with 3 values per square - x, y and size.

Algorithm to find space for an object within a 2d area

I'm building a website which uses jQuery to allow users to add widgets to a page, drag them around and resize them (the page is fixed width and infinite height.) The issue that I'm having is that when adding a new widget to the page I have to find a free space for it (the widgets cannot overlap and I'd like to favour spaces at the top of the page.)
I've been looking at various packing algorithms and none of them seem to be suitable. The reason why is that they are designed for packing all of the objects in to the container, this means that all of the previous rectangles are laid out in a uniform way. They often line up an edge of the rectangle so that they form rows/columns, this simplifies working out what will fit where in the next row/column. When the user can move/resize widgets at will these algorithms don't work well.
I thought that I had a partial solution but after writing some pseudo code in here I’ve realized that it won’t work. A brute force based approach would work, but I'd prefer something more efficient if possible. Can anyone suggest a suitable algorithm? Is it a packing algorithm that I'm looking for or would something else work better?
Thanks
Ok, I've worked out a solution. I didn't like the idea of a brute force based approach because I thought it would be inefficient, what I realized though is if you can look at which existing widgets are in the way of placing the widget then you can skip large portions of the grid.
Here is an example: (the widget being placed is 20x20 and page width is 100px in this example.)
This diagram is 0.1 scale and got messed up so I've had to add an extra column
*123456789A*
1+---+ +--+1
2| | | |2
3| | +--+3
4| | 4
5+---+ 5
*123456789A*
We attempt to place a widget at 0x0 but it doesn't fit because there is a 50x50 widget at that coordinate.
So we then advance the current x coordinate being scanned to 51 and check again.
We then find a 40x30 widget at 0x61.
So we then advance the x coordinate to 90 but this doesn't leave enough room for the widget being placed so we increment the y coordinate and reset x back to 0.
We know from the previous attempts that the widgets on the previous line are at least 30px high so we increase the y coordinate to 31.
We encounter the same 50x50 widget at 0x31.
So we increase x to 51 and find that we can place a widget at 51x31
Here is the javascript:
function findSpace(width, height) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
for (var y = 0; y < heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x < widthOfContainer - width + 1; x++) {
console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
var colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
}
}
y += heightOfShortestInRow - 1;
}
//TODO: Add the widget to the bottom
}
Here is the longer and more less elegant version that also adjusts the height of the container (I've just hacked it together for now but will clean it up later and edit)
function findSpace(width, height,
yStart, avoidIds // These are used if the function calls itself - see bellow
) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
var bottomOfShortestInRow;
var idOfShortestInRow;
for (var y = yStart ? yStart : 0; y <= heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x <= widthOfContainer - width + 1; x++) {
console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
var widgetId = $collider.attr('data-widget-id');
if (!avoidIds || !$.inArray(widgetId, avoidIds)) { // If this is true then we are calling ourselves and we used this as the shortest widget before and it didnt work
bottomOfShortestInRow = colliderBottom;
idOfShortestInRow = widgetId;
}
}
}
y += heightOfShortestInRow - 1;
}
if (!yStart) {
// No space was found so create some
var idsToAvoid = [];
for (var attempts = 0; attempts < widthOfContainer; attempts++) { // As a worse case scenario we have lots of 1px wide colliders
idsToAvoid.push(idOfShortestInRow);
heightOfContainer = $ul.height();
var maxAvailableRoom = heightOfContainer - bottomOfShortestInRow;
var extraHeightRequired = height - maxAvailableRoom;
if (extraHeightRequired < 0) { extraHeightRequired = 0;}
$ul.height(heightOfContainer + extraHeightRequired);
var result = findSpace(width, height, bottomOfShortestInRow, idsToAvoid);
if (result.top) {
// Found a space
return result;
}
// Got a different collider so lets try that next time
bottomOfShortestInRow = result.bottom;
idOfShortestInRow = result.id;
if (!bottomOfShortestInRow) {
// If this is undefined then its broken (because the widgets are bigger then their contianer which is hardcoded atm and resets on f5)
break;
}
}
debugger;
// Something has gone wrong so we just stick it on the bottom left
$ul.height($ul.height() + height);
return { 'left': 0, 'top': $ul.height() - height };
} else {
// The function is calling itself and we shouldnt recurse any further, just return the data required to continue searching
return { 'bottom': bottomOfShortestInRow, 'id': idOfShortestInRow };
}
}
function isOverlapping($obsticles, tAxis, width, height) {
var t_x, t_y;
if (typeof (width) == 'undefined') {
// Existing element passed in
var $target = $(tAxis);
tAxis = $target.position();
t_x = [tAxis.left, tAxis.left + $target.outerWidth()];
t_y = [tAxis.top, tAxis.top + $target.outerHeight()];
} else {
// Coordinates and dimensions passed in
t_x = [tAxis.left, tAxis.left + width];
t_y = [tAxis.top, tAxis.top + height];
}
var overlap = false;
$obsticles.each(function () {
var $this = $(this);
var thisPos = $this.position();
var i_x = [thisPos.left, thisPos.left + $this.outerWidth()]
var i_y = [thisPos.top, thisPos.top + $this.outerHeight()];
if (t_x[0] < i_x[1] && t_x[1] > i_x[0] &&
t_y[0] < i_y[1] && t_y[1] > i_y[0]) {
overlap = this;
return false;
}
});
return overlap;
}

Point in Polygon falsely detected

Derived from this: How to tackle diagonally stacked, rounded image background element hovers?
I made imagemap areas and transformed them for my case, but, now there is a problem with point in polygon hit detection.
It appears that only the bottom right quadrant is always correct, but, only if looking outside the ring - inside the detection might be still be incorrect. Other quadrants, outside the ring, occasionally report a positive hit where it should be false.
Fiddle: http://jsfiddle.net/psycketom/9J4dx/1/
The red lines are drawn from the polygon that's generated from data-map.
The blue line represents the polygon we're currently checking.
The point in polygon function comes from: https://github.com/substack/point-in-polygon
var pointInPolygon = function(point, vs)
{
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point[0], y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
I cannot seem to understand what's the problem here.
Your mapToPolygon function doesn't convert the parsed points from string to number. Because of this, the pointInPolygon function ends up comparing the strings of the coordinates, not the actual coordinates. Using a parseInt on line 31 of the fiddle fixes the problem.
Create an off-screen canvas and use the context's .isPointInPath(x, y) function.
Loop through all of your polygons (in your example you would loop through them in reverse because you have smallest last. The smallest would be the highest level / greatest z-index).
On you get a hit (isPointInPath returns true) stop.
Something like...
var offcanvas = document.createElement("canvas");
...
var x = e.pageX - $ages.offset().left;
var y = e.pageY - $ages.offset().top;
revlayers.each(function() {
var $elm = $(this);
var poly = $elm.data("polygon");
var ctx = offcanvas.getContext("2d");
if(poly.length > 0) {
ctx.beginPath();
ctx.moveTo(poly[0][0], poly[0][1]);
for(var i=1; i<poly.length; i++) {
ctx.lineTo(poly[i][0], poly[i][1]);
}
if(ctx.isPointInPath(x, y)) {
hit.text($elm.attr("href"));
return false; // end the .each() loop
}
}
})

Categories