Move rectangles so they don't overlap - javascript
This is a half programming, half math question.
I've got some boxes, which are represented as four corner points. They are true rectangles, the intersections of two sets of parallel lines, with every line in each set at a right angle to both lines in the other set (just so we're clear.)
For any set of n boxes, how can I efficiently calculate where to move them (the least distance) so that they do not overlap each other?
I'm working in javascript here. Here's the data:
//an array of indefinite length of boxes
//boxes represented as arrays of four points
//points represented as arrays of two things, an x and a y, measured in
//pixels from the upper left corner
var boxes = [[[504.36100124308336,110.58685958804978],[916.3610012430834,110.58685958804978],[916.3610012430834,149.58685958804978],[504.36100124308336,149.58685958804978]],[[504.4114378910622,312.3334473005064],[554.4114378910622,312.3334473005064],[554.4114378910622,396.3334473005064],[504.4114378910622,396.3334473005064]],[[479.4272869132357,343.82042608058134],[516.4272869132358,343.82042608058134],[516.4272869132358,427.82042608058134],[479.4272869132357,427.82042608058134]],[[345.0558946408693,400.12499171846],[632.0558946408694,400.12499171846],[632.0558946408694,439.12499171846],[345.0558946408693,439.12499171846]],[[164.54073131913765,374.02074227992966],[264.54073131913765,374.02074227992966],[264.54073131913765,428.02074227992966],[164.54073131913765,428.02074227992966]],[[89.76601656567325,257.7956256799442],[176.76601656567325,257.7956256799442],[176.76601656567325,311.7956256799442],[89.76601656567325,311.7956256799442]],[[60.711850703535845,103.10558195262593],[185.71185070353584,103.10558195262593],[185.71185070353584,157.10558195262593],[60.711850703535845,157.10558195262593]],[[169.5240557746245,23.743626531766495],[231.5240557746245,23.743626531766495],[231.5240557746245,92.7436265317665],[169.5240557746245,92.7436265317665]],[[241.6776988694169,24.30106373152889],[278.6776988694169,24.30106373152889],[278.6776988694169,63.30106373152889],[241.6776988694169,63.30106373152889]],[[272.7734457459479,15.53275710947554],[305.7734457459479,15.53275710947554],[305.7734457459479,54.53275710947554],[272.7734457459479,54.53275710947554]],[[304.2905062327675,-3.9599943474960035],[341.2905062327675,-3.9599943474960035],[341.2905062327675,50.04000565250399],[304.2905062327675,50.04000565250399]],[[334.86335590542114,12.526345270766143],[367.86335590542114,12.526345270766143],[367.86335590542114,51.52634527076614],[334.86335590542114,51.52634527076614]],[[504.36100124308336,110.58685958804978],[916.3610012430834,110.58685958804978],[916.3610012430834,149.58685958804978],[504.36100124308336,149.58685958804978]],[[504.4114378910622,312.3334473005064],[554.4114378910622,312.3334473005064],[554.4114378910622,396.3334473005064],[504.4114378910622,396.3334473005064]],[[479.4272869132357,343.82042608058134],[516.4272869132358,343.82042608058134],[516.4272869132358,427.82042608058134],[479.4272869132357,427.82042608058134]],[[345.0558946408693,400.12499171846],[632.0558946408694,400.12499171846],[632.0558946408694,439.12499171846],[345.0558946408693,439.12499171846]],[[164.54073131913765,374.02074227992966],[264.54073131913765,374.02074227992966],[264.54073131913765,428.02074227992966],[164.54073131913765,428.02074227992966]],[[89.76601656567325,257.7956256799442],[176.76601656567325,257.7956256799442],[176.76601656567325,311.7956256799442],[89.76601656567325,311.7956256799442]],[[60.711850703535845,103.10558195262593],[185.71185070353584,103.10558195262593],[185.71185070353584,157.10558195262593],[60.711850703535845,157.10558195262593]],[[169.5240557746245,23.743626531766495],[231.5240557746245,23.743626531766495],[231.5240557746245,92.7436265317665],[169.5240557746245,92.7436265317665]],[[241.6776988694169,24.30106373152889],[278.6776988694169,24.30106373152889],[278.6776988694169,63.30106373152889],[241.6776988694169,63.30106373152889]],[[272.7734457459479,15.53275710947554],[305.7734457459479,15.53275710947554],[305.7734457459479,54.53275710947554],[272.7734457459479,54.53275710947554]],[[304.2905062327675,-3.9599943474960035],[341.2905062327675,-3.9599943474960035],[341.2905062327675,50.04000565250399],[304.2905062327675,50.04000565250399]],[[334.86335590542114,12.526345270766143],[367.86335590542114,12.526345270766143],[367.86335590542114,51.52634527076614],[334.86335590542114,51.52634527076614]]]
This fiddle shows the boxes drawn on a canvas semi-transparently for clarity.
You could use a greedy algorithm. It will be far from optimal, but may be "good enough". Here is a sketch:
1 Sort the rectangles by the x-axis, topmost first. (n log n)
2 for each rectangle r1, top to bottom
//check for intersections with the rectangles below it.
// you only have to check the first few b/c they are sorted
3 for every other rectangle r2 that might intersect with it
4 if r1 and r2 intersect //this part is easy, see #Jose's answer
5 left = the amount needed to resolve the collision by moving r2 left
6 right = the amount needed to resolve the collision by moving r2 right
7 down = the amount needed to resolve the collision by moving r2 down
8 move r2 according to the minimum value of (left, right down)
// (this may create new collisions, they will be resolved in later steps)
9 end if
10 end
11 end
Note step 8 could create a new collision with a prior rectangle, which wouldn't be resolved properly. Hm. You may need to carry around some metadata about previous rectangles to avoid this. Thinking...
Keep in mind the box model, given any two rectangles you have to calculate the two boxes width and height, adding their respective margins, paddings, and borders (add the left/right of them to detect collision on the x axis, and top/bottom to detect collision on the y axis), then you can calculate the distance between element 1 and 2 adding the result to their respective coordinate position, for example ((positionX2+totalWidth2) - (positionX1+totalWidth1)) to calculate collision along the X axis. If it is negative, they are overlapping. Once you know this, if they won't overlap by moving them, you can move them normally, otherwise you have to subtract the amount of space they are overlapping from the value you want to move them.
Since the environment is a 2D plane, this should be pretty straightforward. With a library such as jQuery would be a joke, but even in plain js is just basic addiction and subtraction.
Assuming the boxes are aligned to the x and y axis as in your comment, first I'd change the representation of each rectangle to 4 points: top, right, bottom, left and store them as points on the rectangle. Second, let's simplify the problem to "Given n rectangles, where is the nearest point where rectangle r can move to so that it doesn't overlap any other rectangles"? That simplifies the problem a great deal, but also should provide a decent solution. Thus, we have our function:
function deOverlapTheHonkOuttaTheRectangle(rectangle, otherRectangles){
..
}
Now, each other rectangle will disallow a certain range of motion for the original rectangle. Thus, you calculate all of these disallowed moves. From these, you can calculate the disallow shape that overlaps the origin and each other. For example, lets say rect1 disallows a shift of -3px to 5px right and 4px to 10px up, and rect2 disallows -4px to 1px right and -2px to 5px up. rect1 was not considered until rect2 came along, since that one overlaps the origin and rect1. Starting with rect2, you'd have [[-4, -2],[1,-2],[1,5],[-4,5]]. Figuring in rect1 gives [[-4, -2],[1,-2],[1,4],[5,4],[5,10],[-3,10],[-3,5],[-4,5]] (see image below for clarification). You keep building these up for each overlapping disallowed rectangle. Once you have considered all the rectangles, then you can use a distance formula from the origin to get the smallest distance you can move your rectangle and move it.
Finally, you repeat this process for all remaining rectangles.
Related
Drawing a point to point ellipse to allow painting in GUI
I'm implementing some basic annotation draw features, such as arrows. Now I'm a little bit stuck with ellipse. The methods to draw an ellipse usually address using it's two diameters and eventually a rotation: However I want to display the ellipse between the point user clicked and the one he's hovering, therefore I need a function that calculates diameters and rotation based on two points: How would I do that? Can it be achieved with sufficient performance (as it renders during mouse-hovering)?
the steps you shoul follow: get the angle of the line (from this post: get angle of a line from horizon) rotate the canvas or at least the part you currently drawing (live demo here: http://www.html5canvastutorials.com/advanced/html5-canvas-transform-rotate-tutorial) draw an ellipse in canvas (http://www.scienceprimer.com/draw-oval-html5-canvas) the resulted ellipse will be transformed as described
It can be done in the same way that it is normally done, just using different math to calculate the shape. Without writing the entire code for you, you can start by having an event trigger when the user clicks the mouse button down. The function will copy the users x and y position based on the screen. Then there is a second function which will handle mouse movement. This function will keep track of the x and y coords of the mouse while it is in motion. The final function will be a mouse up event, when a user lifts their finger from the mouse button (assuming this is when the event should be finished). Using the initial and final position of the x and y coordinates, you can calculate the length of the line the user created. That line is the long diameter of the ellipse. Half this number for the large radius. Then use whatever ratio you are using to calculate the smaller radius from the larger one. Then create an ellipse based on these numbers. For the math: Suppose your first point is x1,y1 and the end point is x2,y2 I'm also assuming that we have a line going from bottom-left to top-right Distance between two points = sqrt((x2-x1)^2 + (y2-y1)^2) ---> (we will call this d1) half of this is the length of the large radius ---> (we will call this r1) Midpoint formula = ((x1+x2)/2 , (y1+y2)/2) ---> axis of rotation (we will call it (m1, m2)) distance from midpoint to end is just the radius radius is now the hypotenuse of constructed plane, y2-m2 is height of right triangle. Find the angles between midpoint and one end of larger radius - sin((y2-m2)/r1). Angle of smaller radius is this angle + pi/4 radians. calculate length of smaller radius based on ratio.
Rotate a Two.js object in its position
I have a large circle with smaller ones inside made using two.js. My problem is that these two do not rotate in their own place but in the top left axis. I want the group of circles (circlesGroup) rotate only inside the large one in a static position. The circlesGroup and the large circle are grouped together as rotatoGroup. two.bind('update', function(frameCount, timeDelta) { circlesGroup.rotation = frameCount / 120; }); two.bind('update', function(frameCount, timeDelta) { rotatoGroup.rotation = frameCount / 60; }); The whole code is in CodePen.
All visible shapes when invoked with two.make... ( circles, rectangles, polygons, and lines ) are oriented in the center like this Adobe Illustrator example: When this shape's translation, rotation, or scale change those changes will be reflected as transformations about the center of the shape. Two.Groups however do not behave this way. Think of them as display-less rectangles. They're origin, i.e group.translation vector, always begins at (0, 0). In your case you can deal with this by normalizing the translation your defining on all your circles. Example 1: Predefined in normalized space In this codepen example we're defining the position of all the circles around -100, 100, effectively half the radius in both positive-and-negative x-and-y directions. Once we've defined the circles within these constraints we can move the whole group with group.translation.set to place it in the center of the screen. Now when the circles rotate they are perceived as rotating around themselves. Example 2: Normalizing after the fact In this codepen example we're working with what we already have. A Two.Group that contains all of our shapes ( the bigger circle as well as the array of the smaller circles ). By using the method group.center(); ( line 31 ) we can normalize the children of the group to be around (0, 0). We can then change the translation of the group in order to be in the desired position. N.B: This example is a bit complicated because it invokes underscore's defer method which forces the centering of the group after all the changes have been registered. I'm in the process of fixing this.
Constructing a rectangle from smaller rectangles
For the last few hours I've been attempting to construct a rectangle from smaller rectangles on any angle; I imagined doing so like this http://i.stack.imgur.com/Ymakk.png [ Where the black lines represent the outline of the constructed rectangle and the red lines represent the triangles that construct it. The red rectangles have to be completely straight also. ] Obviously it wouldn't look very defined unless I have a very small increment when trying to render it but the end product would hopefully look like a rectangle at a predetermined angle. The screen is set up so that the top, left hand corner is (0, 0) and the bottom, right is (1, 1). How would I do this? Not specifically looking for actual code, pseudo-code or even an explanation, maybe some math.. would be brilliant! Thanks in advance.
Here's a bit of geometry for you: If your rectangle is perfectly aligned, you can just draw one big rectangle and you win. If it's slighted at any angle at all, you can express the lines as simple equations y = m * x + c where you insert x and y of both edges to calculate m and c (a very simple procedure actually), or you can calculate that m = tan(angle) Then you can "just" walk down the y coordinates and get the corresponding x positions on the left and right lines that belong to the rectangle by inserting the y, c and m values to the equation and solving for x. If you look at your rectangle picture, you can see that there's basically three parts: the first part goes from the bottom to the lower left edge. It follows the long side to the left and encounters an edge on the left side. After that you follow both short sides to the right and then you encounter the upper right edge and go up to the upper left corner (of course you could do this from the top down, too) You'll need to carefully check if you've reached the edge between two lines on the left or right side and switch your values for m and c at the appropiate moments. I think this should get you close to a solution
HTML5: inaccurate positioning of rectangles
I'm using canvas of HTML5 to create a "preview" image which mainly consists of some rectangles and simple lines. Works fine so far, but there's one problem I cannot fix somehow. Presume the following situation: context.fillStyle = "rgba(0,0,0,0.75)"; context.fillRect(100.64646,100,50.94967,20); context.fillRect(100.64646+50.94967,100,100,20); So I'm drawing 2 rectangles with some opacity. The x-starting coordinate plus the x-length of the first rect is equal to the x-starting coordinate of the second rect, so in theory they should collide without any margin between. Sadly, the result is different: (see http://files.clemensfreitag.de/thin_spacing.jpg) There's a very tiny spacing between the boxes, and the background color is visible. But: This problem doesn't occur if the coordinates and lengths are integer values. Is there any way to get it done by using float values? Converting them to integers before drawing might be acceptable in my application, but I'm just wondering why this should not work with floats. Best, Clemens
What you're seeing is the result of overlaying two opaque colors. When the first rectangle ends at 151.59613, the rectangle is automatically antialiased, filling in the rightmost column with rgba(0,0,0,0.4470975). When the second rectangle starts at the same x coordinate, it is also antialiased, filling in the leftmost column (the same as the first rectangle's rightmost) with rgba(0,0,0,0.3029025). The two values do add up to rgba(0,0,0,0.75), but that's not how they are blended. Instead, the second color (rgba(0,0,0,.3029025)) is drawn on top of the first, resulting in rgba(0,0,0,0.4470975+(1-0.4470975)*0.3029025) = rgba(0,0,0,0.61457305). So there isn't actually a gap between the two rectangles, but rather a 1px column that is a slightly lighter shade of grey. Similarly, if you were using solid colors then the second rectangle's antialiased column would overwrite the first's, resulting in an even lighter shade of grey in the "gap". The issue does not show up with integer values because no antialiasing is required - each rectangle ends at the edge of a pixel. It looks like none of the globalCompositeOperation settings fix this, and turning off antialiasing would sometimes result in a 1px gap, so I think your simplest solution is to force integer values (alternatively, you could clear that column then fill it in with the desired color).
This problem is related to the way objects are drawn on a float based grid (especially vertical and horizontal lines and thus rects). See there for an explanation and a schema : http://canop.org/blog/?p=220 Depending on the size of your objects, you need to use integer or mid-integer coordinates and sizes for your shapes, the goal being to fill complete pixels in both dimensions. For example : use a mid-integer for a thin line (one pixel width) use an integer coordinate for a 2 pixels wide line (and extend the logic for rects)
Calculate new width when skewing in canvas
I'm using canvas for a project and I have a number of elements that I'm skewing. I'm only skewing on the y value and just want to know what the new width of the image is after skewing (so I can align it with another canvas element). Check out the code below to see what I mean ctx.save(); //skew the context ctx.transform(1,0,1.3,0,0,0); //draw two images with different heights/widths ctx.drawImage(image,0,0,42,60); ctx.drawImage(image,0,0,32,25); The goal would be to know that the 42 by 60 image was now a X by 60 image so I could do some translating before drawing it at 0,0. It's easy enough to measure each image individually, but I have different skew values and heights/widths throughout the project that need to be align. Currently I use this code (works decently for images between 25 and 42 widths): var skewModifier = imageWidth*(8/6)+(19/3); var skewAmount = 1.3; //this is dynamic in my app var width = (skewModifier*skewAmount)+imageWidth; As images get wider though this formula quickly falls apart (I think it's a sloping formula not a straight value like this one). Any ideas on what canvas does for skews?
You should be able to derive it mathematically. I believe: Math.atan(skewAmount) is the angle, in radians, that something is skewed with respect to the origin. So 1.3 would skew the object by 0.915 radians or 52 degrees. So here's a red unskewed object next to the same object skewed (painted green). So you have a right triangle: We know the origin angle (0.915 rads) and we know the adjacent side length, which is 60 and 25 for your two images. (red's height). The hypotenuse is the long side thats being skewed. And the opposite side is the triangle bottom - how much its been skewed! Tangent gets us opposite / adjacent if I recall, so for the first one: tan(0.915) = opposite / 60, solving for the opposite in JavaScript code we have: opposite = Math.tan(0.915)*60 So the bottom side of the skewed object starts about 77 pixels away from the origin. Lets check our work in the canvas: http://jsfiddle.net/LBzUt/ Looks good to me! The triangle in question of course is the canvas origin, that black dot I painted, and the bottom-left of the red rectangle, which is the original position that we're searching for before skewing. That was a bit of a haphazard explanation. Any questions?
Taking Simon's fiddle example one step further, so you can simply enter the degrees: Here's the fiddle http://jsfiddle.net/LBzUt/33/