Arranging rectangles within a boundary without overlap and x contraints - javascript

I am trying to arrange an unknown number of rectangles so that they dont overlap each other. There are a number of constraints when rearranging rectangles:
Can ONLY move in the positive y (up) direction with the exception the
condition where moving up will push the rectangle outside of the
container boundary.
CANNOT move in the x (left or right) direction We should get some
reasonable padding in between rectangles on all sides.
The top most rectangle should be the first rectangle (denoted by a label in the jsbin link)
I wrote a small something that would generate the main problem here at jsbin. So far the only thing that comes to my mind is a situation where I iterate through these rectangles back and forth. I was wondering if anyone can suggest an approach or better yet point to an existing solution.

You need to compute the vertical position of each rectangle using the height of the preceding rectangles. It may be useful to check if the problem has a solution.
// Generate random rectangles, with the vertical position set to zero
var padding = 5;
var num_rectangles = 10;
var rectangles = [];
for (var k = 0; k < num_rectangles; k += 1) {
rectangles.push({
x: 50 * Math.random(),
y: padding,
width: Math.max(50 * Math.random(), 20),
height: Math.max(50 * Math.random(), 20)
});
}
// Update the vertical position of the item j as the sum of the heigths
// of the rectangles 0, ..., j - 1
for (var j = 1; j < num_rectangles; j += 1) {
rectangles[j].y = rectangles[j - 1].y + rectangles[j - 1].height + padding;
}
And then just draw the rectangles as usual. I wrote a small example http://jsfiddle.net/FuepP/2/

The only thing I can think of at the moment is basically a branch and bound search. Starting from the bottom, iteratively resolve colliding pairs by pushing one or the other rectangle up (branching). If you go above the limit, backtrack to the previous branch.
I wouldn't be surprised if solving this problem was NP-Complete. It's a more constrained version of the bin packing problem. On the other hand, over-constraining problems tends to make them very easy so maybe it's not NP-Complete. I tried to think of a reduction for a few minutes but didn't come up with anything.

Related

Canvas - How to find if a point is above or under a diagonale line?

In a 2D game, i need to find if an object is above or under a diagonale line.
Anyone knows how to do this ?
(i use createJS framework)
OK, scrap my previous answer and use line intersection instead. Shoot a line from the point to test straight up. If there is an intersection the point is below, if none, the point is either above or to the side of the line.
To avoid side cases (no pun), extend the original line using interpolation.
Here is a function to do line intersection. To do linear interpolation of the original line simply use some extreme values:
var tx1 = x1 + (x2-x1) * -51000;
var ty1 = y1 + (y2-y1) * -51000;
var tx2 = x1 + (x2-x1) * 53200;
var ty2 = y1 + (y2-y1) * 53200;
Update I was a bit in a hurry this morning so here's a small update. As blindman67 points out, you can use just the d in the linked intersection function and check s/t if they are in the normalized range (or just use cross product - see his answer it that is a better fit).
Build a triangle using the upper coordinates to create a shape. For example, if your line look like:
You can create a shape of if using x2 and y1:
Now simply add the triangle to the path and do a isPointInPath(x, y), if true it's above, if false it's below.
If you need to check below reverse the process.
(wowa! a lot of arrows there... but you'll get the idea :) )
Edge cases (pun intended): if point is very close to one of the ends -> just extend the line, or make polygon extending (x1,y1) up to edge of the area.
Actually, thinking about it: triangles may not be so suitable, rather, use the upper edge of the canvas as a segment of polygon, then the next segment would be vertical line down to the end of the diagonal line, the the final segment from the beginning of the diagonal line to the upper left side of the canvas. I'm just too lazy to redo the graphics but you get the idea..
Use the cross product of the point and the line.
You need to move the whole coord system to the start of the line and then get the cross product of the line and the point. If the result is negative then the point is left of the line, if positive then the point is right of the line, if zero then the point is on the line.
// the point
var px = 100;
var py = 100;
// the line
var lx1 = 20;
var ly1 = 20;
var lx2 = 320;
var ly2 = 120;
// move line end and point so that line start is at 0,0
lx2 -= lx1;
ly2 -= ly1;
px -= lx1;
py -= ly1;
// get cross product
var cross = lx2 * py - ly2 * px;
if(cross < 0){ // point is to the left (anticlockwise)
}else if(cross > 0){ // point is to the right (clockwise)
}else{ // cross must be zero then point is on the line
}

How to rotate an array of canvas rectangles

I'm creating a Pentomino puzzle game for a final project in a class I'm taking. I've created all dozen of the required puzzle pieces and can drag those around here. And I've tried this code to rotate the array (without using canvas.rotate() & located at the very bottom of the fiddle), it basically swaps the X & Y coordinates when drawing the new piece:
var newPiece = targetPiece;
pieces.splice(pieces.indexOf(targetPiece), 1);
targetPiece = null;
console.log(newPiece);
var geometry = [];
for (var i = 0; i < newPiece.geometry.length; i++) {
geometry.push([newPiece.geometry[i][3], newPiece.geometry[i][0]]);
}
var offset = [newPiece.offset[1], newPiece.offset[0]];
console.log(geometry);
console.log(offset);
newPiece.geometry = geometry;
newPiece.position = geometry;
newPiece.offset = offset;
pieces.push(newPiece);
console.log(pieces);
for (var j = 0; j < pieces.length; j++) {
draw(pieces[j]);
}
This doesn't work properly, but has promise.
In this fiddle, I've isolated the problem down to a single piece and tried to use canvas.rotate() to rotate the array by double clicking, but what's actually happening is it's rotating each piece of the array (I think), which results in nothing happening because each block of the array is just a 50x50 rectangle and when you rotate a square, it still looks just like a square.
function doubleClickListener(e) {
var br = canvas.getBoundingClientRect();
mouse_x = (e.clientX - br.left) * (canvas.width / br.width);
mouse_y = (e.clientY - br.top) * (canvas.height / br.height);
var pieceToggle = false;
for (var i = 0; i < pieces.length; i++) {
if (onTarget(pieces[i], mouse_x, mouse_y)) {
targetPiece = pieces[i];
rotate(targetPiece);
}
}
}
function rotate() {
targetPiece.rotationIndex = targetPiece.rotationIndex === 0 ?
1 : targetPiece.rotationIndex === 1 ?
2 : targetPiece.rotationIndex === 2 ?
3 : 0;
for (var j = 0; j < pieces.length; j++) {
draw(pieces[j]);
}
}
Just FYI, I've tried creating the puzzle pieces as individual polygons, but could not figure out how to capture it with a mousedown event and move it with mousemove, so I abandoned it for the canvas rectangle arrays which were relatively simple to grab & move.
There's a brute force solution to this, and a total rewrite solution, both of which I'd rather avoid (I'm up against a deadline-ish). The brute force solution is to create geometry for all possible pieces (rotations & mirroring), which requires 63 separate geometry variants for the 12 pieces and management of those states. The rewrite would be to use fabric.js (which I'll probably do after class is over because I want to have a fully functional puzzle).
What I'd like to be able to do is rotate the array of five blocks with a double click (don't care which way it goes as long as it's sequential 90° rotations).
Approaching a usable puzzle:
With lots of help from #absolom, here's what I have, you can drag with a mouse click & drag, rotate a piece by double clicking it, and mirror a piece by right clicking it (well, mostly, it won't actually rotate until you next move the piece, I'm working on that). The Z-order of the pieces are manipulated so that the piece you're working with is always on top (it has to be the last one in the array to appear on top of all the other pieces):
Pentominoes II
The final solution
I've just handed the game in for grading, thanks for all the help! There was a lot more tweaking to be done, and there are still some things I'd change if I rewrite it, but I'm pretty happy with the result.
Pentominoes Final
Quick & Dirty:
The quick & dirty solution is when 2+ pieces are assembled you create a single image of them (using an in-memory canvas). That way you can move / rotate the 2-piece-as-1-image as a single entity.
More Proper:
If the 2+ piece assembly must later be disassembled, then you will need the more proper way of maintaining transformation state per piece. That more proper way is to assign a transformation matrix to each piece.
Stackoverflow contributor Ken Fyrstenberg (K3N) has coded a nice script which allows you to track individual polygons (eg your rects) using transformation matrices: https://github.com/epistemex/transformation-matrix-js
Does this code do what you need? The rotate method looks like this now:
function rotate(piece) {
for (i = 0; i < piece.geometry.length; i++) {
var x = piece.geometry[i][0];
var y = piece.geometry[i][2];
piece.geometry[i][0] = -y;
piece.geometry[i][3] = x;
}
drawAll();
}
I simplified how your geometry and positioning was handled too. It's not perfect, but it can gives you some hints on how to handle your issues.
Please note that this solution works because each piece is composed of blocks with the same color and your rotations are 90 degrees. I only move the blocks around to simulate the rotation but nothing is rotated per-se. If you build your pieces differently or if you need to rotate at different angles, then you would need to go with another approach like transformation matrices.
UPDATE
Here is a better solution: fiddle

Plotting points on segment of circumference

This one requires a bit of visualisation, so sorry if my explanation sucks.
So, I have a central point at 0,0. From this point, I am plotting random points on its circumference, at a radius of 350 pixels (random number). For this I am using this code:
var angle = Math.random()*Math.PI*2;
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;
The parent.position this is because each point that is plotted also acts as a central node, which has children that act as nodes and so on. This just sets the position of the new node relative the position of its parent.
So this code works perfectly well for the central node. The problem is that once you've branched away from the centre, you want to continue moving in a particular direction to avoid a big cluster of nodes interfering with each other. So, whereas this code plots a point on the circumference, I need to be able to plot a point on a segment of the circumference. I'm thinking maybe about a third of the circumference should be accessible. The other obstacle is that this has to be the CORRECT segment of the circumference i.e If the nodes are branching upwards, I don't want the segment to be the bottom half of the circumference, the branch needs to continue moving in the upwards direction.
I can establish a general direction based on the position of the new parent node relative to the position of its parent. But does anyone have any ideas of how to use this data to reduce the field to the a segment in this direction?
Let me know if that made no sense, it's kinda hard to explain without diagrams.
I think one easy way of doing that would be to split your circle in n segments (each covering 2*PI / n angle). You could set n to whatever you want, depending on how precise you want to be. Then when you calculate a new point x, first get the segment in which x.parent is (relative to its own parent), and use that to put x in the same section wrt x.parent. You could then have something like this:
var getSection = function(point) {
var parent = point.parent;
var angle = Math.acos((point.x - parent.x) / radius) % (Math.PI*2);
var section = Math.floo(angle / (Math.PI * 2 / n))
return section;
}
var section = getSection(parent); // return the index of the section
var angle = (Math.random() + section) * Math.PI * 2 / n
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;

Collision detection on a canvas in JavaScript

I'm working on a game for a university assignment. The idea is that you defend the centre circle from the incoming asteroids (lines) by drawing a line (click, drag & release to draw a line) which blocks them. An asteroid hitting a line should destroy both the asteroid the line.
The problem I'm currently having is that the collision isn't being detected.
I have arrays of objects of both lines & asteroids. The lines consist of simply start & end x & y, the asteroids consist of a random speed & a random angle (their incoming angle) - the context is rotated, the asteroid drawn, & then it reset for the next line.
To detect collision, I use getImageData & check in front of the asteroids however many pixels the line will progress in that iteration (basically, their speed) & if the colour is red, it will destroy the asteroid - I haven't got round to destroying the line yet, will tackle that hurdle when I come to it (suggestions are welcome though).
function asteroids_draw() {
for (var i = 0; i < asteroids.length; i++) {
// Drawing setup
context.save();
context.translate(width / 2, height / 2);
context.rotate(asteroids[i].angle);
// Detecting close asteroids
if ((asteroids[i].distance - asteroids[i].speed) < planet.size) {
asteroids.splice(i, 1);
game_life_lost();
context.restore();
return;
} else if ((asteroids[i].distance - asteroids[i].speed) < 150){
asteroids[i].colour = '#FF0000';
}
// Scanning ahead for lines
for (var j = 0; j < asteroids[i].speed; j++) {
if (context.getImageData(asteroids[i].distance - j, 0, 1, 1).data[0] == 255) {
asteroids.splice(i, 1);
context.restore();
return;
}
}
// Drawing asteroid
context.beginPath();
context.moveTo(asteroids[i].distance -= asteroids[i].speed, 0);
context.lineTo(trig, 0);
context.strokeStyle = asteroids[i].colour;
context.stroke();
context.closePath();
context.restore();
}
}
The problem is, the asteroids never collide with the lines & I can't for the life of me see why, or see another simple way of doing it. Any advice would be much appreciated, thanks in advance.
I think your problem is that when you rotate the context, previously drawn items(lines) don't get rotated, only objects drawn after the rotation are rotated. See this page for more info.
You could try performing your asteroid/line intersection test before you translate and rotate the canvas, and use cosine and sine to find the x and y coordinates of the pixels you want to get image data from.
var pixelLocation =
[Math.cos(asteroids[i].angle) * j, Math.sin(asteroids[i].angle) * j];
if (context.getImageData(pixelLocation[0], pixelLocation[1], 1, 1).data[0] == 255) {
Just make sure your angle is in radians before passing to cos and sin.
I thought about the problem some more, & realised this method of doing things definitely isn't the best way. It should be doable without a view - a la Model View Controller design pattern. The best way to solve it would be to use maths!
There's simple maths for the intersection of two lines, but this needs intersection of two lines in a range. I found an algorithm which simplifies this further, using eight coordinates - the start x & y & the end x & y of the two lines.
I've posted the results. Thanks for the help.

My collision detection algo seems to trigger even before the objects touched

I wrote a very simple collision detection demo:
http://jsfiddle.net/colintoh/UzPg2/5/
As you can see, the objects sometimes doesn't connect at all but yet the collision is being triggered. The radius for the balls are 10px so the algo triggered the collision whenever the distance between two balls center is less than 20px. I reduced it to 18px for a better visual but the empty collision still happens randomly. Am I doing something wrong?
It looks like you are not using the right formula for distance between two points. See http://www.purplemath.com/modules/distform.htm for a full explanation.
You are doing this:
this.ballCollide = function(balli) {
if (Math.abs((this.x) - (balli.x)) < (2*radius - buffer)) {
if (Math.abs((this.y) - (balli.y)) < (2*radius - buffer)) {
// Do collision
}
}
};
That's a square bounding box, not a circular one. To get a circular bounding box, you can do something like this, based on the formula in the referenced web page:
this.ballCollide = function(balli) {
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
if (Math.sqrt(deltax * deltax + deltay * deltay) < 2 * radius - buffer) {
// Do collision
}
};
See http://jsfiddle.net/UzPg2/14/ for a working example.
Note that a perfect circular bounding box is a much slower algorithm than a square bounding box approximation.
Following Jarrod Roberson's point (a perfect circle is always inside a perfect square), you'd do that by basically combining your original code with the code I posted, like this (and you could combine them both into one conditional switch if you wanted to):
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
var dist = 2 * radius - buffer;
if (Math.abs(deltax) < dist && Math.abs(deltay) < dist) {
if (Math.sqrt(deltax * deltax + deltay * deltay) < dist) {
// Do collision
}
}
See http://jsfiddle.net/UzPg2/21/ for a working example (I've left the buffer as your variable is called at 2, but I personally think it looks better with a value of 1).
There are also many other ways you can optimize this for speed if you need to, but Jarrod's suggestion gives you the biggest immediate speed boost.
You're only checking for collisions on two axis, x and y. You need to use Pythagoras' theorem to detect on all axis at the cost of efficiency. For example.
Your algorithm will detect a collision around the point where these two balls are, since if you draw a tangent line along the x or y axis from one ball it goes through the other ball: http://jsfiddle.net/XpXzW/1/
Here you can see where they should actually collide:
http://jsfiddle.net/wdVmQ/1/
If you change your collision detection algorithm to check for perfect collisions (it will be less efficient) you can get rid of your buffer too:
http://jsfiddle.net/ucxER/
(Using Pythagoras' theorem the formula for a collision is:
Math.sqrt((this.x - balli.x)*(this.x - balli.x)
+ (this.y - balli.y)*(this.y - balli.y)) < 2*radius
Also what Jarrod commented is very smart. You can speed it up by using a technique like that. Since the square root is only calculated when the balls are close to each other:
http://jsfiddle.net/bKDXs/

Categories