How to expel a rect from inside another rect - Javascript - javascript

I am trying to make rect1 move out from inside rect2. I have tried to look around for an awsner but i am unable to find a satisfactory awnser. I can detect the rectangles intersecting, but i cannot expel rect1 so it goes outside rect2. Can you provide some code to help me do this? I will provide my detection code so far.
Code:
var DoCollision = function(rect1, rect2, objectToMove){
if (rect1.x + rect1.w > rect2.x &&
rect1.x < rect2.x + rect2.w &&
rect1.y + rect1.h > rect2.y &&
rect1.y < rect2.y + rect2.h){
// Expel rect1 from rect2 here using objectToMove as the object being expelled (In this case, rect1).
};
};
Thank you for responding if you do.
i should tell you the bigger picture. I am trying to make a function where i input 3 rect objects, to test if they are colliding or not, and if so, i want the third rect object to move accordingly. For example, the function parameters are rect1, rect2, rect1, meaning when rect1 intersects rect2 on the left side, i want the third parameter rect1 to move left

One approach would be to identify the minimum amount needed to move in either X or Y directions and then move that amount. This does not take any bounding rectangles into account:
function doCollision(rect1, rect2, objectToMove){
if (rect1.x + rect1.w > rect2.x &&
rect1.x < rect2.x + rect2.w &&
rect1.y + rect1.h > rect2.y &&
rect1.y < rect2.y + rect2.h){
if (objectToMove === rect1) {
moveOutside(objectToMove, rect2);
}
else if (objectToMove === rect2) {
moveOutside(objectToMove, rect1);
}
};
};
function moveOutside(rectToMove, otherRect) {
// Determine if the overlap is due more to x or to y,
// then perform the appropriate move
var moveOverOtherX = rectToMove.x + rectToMove.w - otherRect.x;
var otherOverMoveX = otherRect.x + otherRect.w - rectToMove.x;
var moveOverOtherY = rectToMove.y + rectToMove.h - otherRect.y;
var otherOverMoveY = otherRect.y + otherRect.h - rectToMove.y;
var minOver = Math.min(moveOverOtherX, otherOverMoveX, moveOverOtherY, otherOverMoveY);
if (minOver == moveOverOtherX) {
rectToMove.x = otherRect.x - rectToMove.w;
}
else if (minOver == otherOverMoveX) {
rectToMove.x = otherRect.x + otherRect.w;
}
else if (minOver == moveOverOtherY) {
rectToMove.y = otherRect.y - rectToMove.h;
}
else {
rectToMove.y = otherRect.y + otherRect.h;
}
rectToMove.update();
}
See it in a fiddle here.

A simple solution:
var DoCollision = function(rect1, rect2, objectToMove){
if (rect1.x + rect1.w > rect2.x &&
rect1.x < rect2.x + rect2.w &&
rect1.y + rect1.h > rect2.y &&
rect1.y < rect2.y + rect2.h){
objectToMove.x = Math.max(rect1.x + rect1.w, rect2.x + rect2.w);
objectToMove.y = Math.max(rect1.y + rect1.h, rect2.y + rect2.h);
};
};
But note that this will only work if there are only two rectangles in your entire dataset. If there are many rectangles, moving rect1 to avoid a collision with rect2 may create a collision with some other rectangle(s). In general the problem of moving objects the minimal distance to avoid collisions with all other objects is very hard (probably NP-hard, though I haven't verified this).
EDIT: Moving a colliding object back away from the collision is probably best done by checking whether the collision will happen BEFORE you change the position of the moving object (i.e., compute the desired position, check if it will cause a collision, and if so don't move the object at all). But if you must do it afterwards, you can generalize the above approach:
var DoCollision = function(rect1, rect2, objectToMove){
var intersectInX = rect1.x + rect1.w > rect2.x &&
rect1.x < rect2.x + rect2.w;
var intersectInY = rect1.y + rect1.h > rect2.y &&
rect1.y < rect2.y + rect2.h;
var collide = intersectInX && intersectInY;
if ( ! collide ) { return; }
if (intersectInX) {
var leftmostEdge = Math.min(rect1.x, rect2.x);
var rightmostEdge = Math.max(rect1.x, rect2.x);
if (objectToMove.x > leftmostEdge) { // move right rectangle
objectToMove.x = rightmostEdge;
} else { // move left rectangle
objectToMove.x = leftmostEdge - objectToMove.w;
}
}
if (intersectInY) {
var topmostEdge = Math.min(rect1.y, rect2.y);
var bottommostEdge = Math.max(rect1.y, rect2.y);
if (objectToMove.y > topmostEdge) { // move bottom rectangle
objectToMove.y = bottommostEdge;
} else { // move top rectangle
objectToMove.y = topmostEdge - objectToMove.h;
}
}
};
This doesn't address 2 cases in which you need to decide how you want the rectangles to behave:
1) If one rectangle completely encloses the other. They will be moved apart, but not necessarily in the way that you want.
2) If the movement causes intersection with the outer border.

Related

How to detect the side on which collision occured

This is my first post so I'm trying to make my problem as clear as possible. I'm making a game and I want to improve my collision detection. This is because I want to check what side is being hit and stop the player from moving past it without using something general like if(collision(player, enemy)) player.x = enemy.x - player.w(width) because if the player were to collide with the top it wouldn't keep the player on top.
In the code it checks if any one of the statements is true and then returns it but it doesn't tell me which statement was the one that was equal to true so I can stop the player from moving accordingly, if that makes sense. If you have a more efficient collision detection for me to use it would be greatly appreciated.
I've already tried to make a position variable to be equal to whatever side gets collided into and then stop the player from moving past it but it only works for the left side and won't let my player jump over the enemy or block.
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
//Only works for the left side
if(collision(player, enemy)) player.x = enemy.x - player.w
I expect it to be able to tell me what side is being collided into and then either stop the player from moving past/into it and for the player to be able to be on top of the block/enemy without just being pushed to the left.
You'll want to calculate the distance between the x's and y's and also use the minimum distance that they could be colliding along each axis to find the depth along both axes. Then you can pick the smaller depth and move along that one. Here's an example:
if(collision(player, enemy)){
// Most of this stuff would probably be good to keep stored inside the player
// along side their x and y position. That way it doesn't have to be recalculated
// every collision check
var playerHalfW = player.w/2
var playerHalfH = player.h/2
var enemyHalfW = enemy.w/2
var enemyHalfH = enemy.h/2
var playerCenterX = player.x + player.w/2
var playerCenterY = player.y + player.h/2
var enemyCenterX = enemy.x + enemy.w/2
var enemyCenterY = enemy.y + enemy.h/2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if(depthX != 0 && depthY != 0){
if(Math.abs(depthX) < Math.abs(depthY)){
// Collision along the X axis. React accordingly
if(depthX > 0){
// Left side collision
}
else{
// Right side collision
}
}
else{
// Collision along the Y axis.
if(depthY > 0){
// Top side collision
}
else{
// Bottom side collision
}
}
}
}
Working example
Here's a working example that you can play around with. Use the arrow keys to move the player around.
player = {
x: 9,
y: 50,
w: 100,
h: 100
}
enemy = {
x: 100,
y: 100,
w: 100,
h: 100
}
output = document.getElementById("collisionType");
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d")
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
function draw() {
ctx.clearRect(0, 0, 400, 400)
ctx.lineWidth = "5"
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.rect(player.x, player.y, player.w, player.h);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h);
ctx.stroke();
}
function handleCollision() {
if (collision(player, enemy)) {
var playerHalfW = player.w / 2
var playerHalfH = player.h / 2
var enemyHalfW = enemy.w / 2
var enemyHalfH = enemy.h / 2
var playerCenterX = player.x + player.w / 2
var playerCenterY = player.y + player.h / 2
var enemyCenterX = enemy.x + enemy.w / 2
var enemyCenterY = enemy.y + enemy.h / 2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if (depthX != 0 && depthY != 0) {
if (Math.abs(depthX) < Math.abs(depthY)) {
// Collision along the X axis. React accordingly
if (depthX > 0) {
output.innerHTML = "left side collision"
} else {
output.innerHTML = "right side collision"
}
} else {
// Collision along the Y axis.
if (depthY > 0) {
output.innerHTML = "top side collision"
} else {
output.innerHTML = "bottom side collision"
}
}
}
} else {
output.innerHTML = "No collision"
}
}
keyStates = []
function handleKeys() {
if (keyStates[39]) {
player.x += 2 //Move right
} else if (keyStates[37]) {
player.x -= 2 //Move left
}
if (keyStates[38]) {
player.y -= 2 //Move up
}
if (keyStates[40]) {
player.y += 2 //Move down
}
}
function main() {
handleKeys();
draw();
handleCollision();
window.requestAnimationFrame(main);
}
window.onkeydown = function(e) {
keyStates[e.keyCode] = true
}
window.onkeyup = function(e) {
keyStates[e.keyCode] = false
}
main();
<h2 id="collisionType"></h2>
<canvas id="canvas" width='300' height='300'></canvas>
Reacting to the collision
Now that you know the side the collision happened on, it should be fairly trivial to decide how to react. It would be very similar to what you are currently doing for the left side just flip some signs around and change the axis.
Other Considerations
You may want to take into account your player's velocity (if it has one) otherwise the detection may fail.
If the player's velocity is too high, it might 'tunnel' through the enemy and no collision will be detected.
The player's movement can also look jittery if the velocity is not stopped upon collision
Can your objects rotate or have more than 4 sides? If so, you'll probably want to use another method as described below.
Here's a good answer to another post that talks in depth about collision engines
Other Methods
As for other collision detection methods, there's quite a few but one that comes to mind is Separating Axis Theorem which is a little more complex than what you have but will work with more complex convex shapes and rotation. It also tells you the direction and distance needed to move to resolve the collision. Here's a site that has interactive examples and goes in-depth on the subject. It doesn't appear to give a full implementation but those can be found other places.

Handling D3v3 Labels Overlapping

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

PIXIJS detect overlapping between two DisplayObjectContainer

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;

how to stop an object when it collides with another object in javascript canvas

OK, so I am making a Pacman game using HTML5 . the problem is whenever I hit one of the brick blocks I want the sprite to stop moving but it keeps going until it hits the left most brick object.
how do I fix this? please help... here is the code I'm using to make the sprite stop.
here is all my code, if you have time, please parse it, and tell me what I have done wrong.
function init(){
var canvas=document.getElementById("ctx");
var ctx = canvas.getContext("2d");
var player = {sx:6,sy:6,sw:15,sh:15,x:230,y:377,w:20,h:20}
var ss = new Image();
ss.src="SS.png";
var right=false,left= true,up = false,down = false
var b = [{x:0,y:0,w:25,h:((canvas.height/2)-25)},{x:0,y:((canvas.height/2)),w:25,h:((canvas.height/2))},{x:50,y:25,w:50,h:50},{x:125,y:25,w:75,h:50},{x:225,y:0,w:25,h:75},{x:275,y:25,w:75,h:50},{x:375,y:25,w:50,h:50},{x:50,y:100,w:50,h:25},{x:125,y:100,w:25,h:125},{x:125,y:150,w:75,h:25},{x:175,y:100,w:125,h:25},{x:225,y:125,w:25,h:50},{x:325,y:100,w:25,h:125},{x:275,y:150,w:75,h:25},{x:375,y:100,w:50,h:25},{x:25,y:150,w:75,h:75},{x:375,y:150,w:75,h:75},{x:375,y:250,w:75,h:75},{x:25,y:250,w:75,h:75},{x:125,y:250,w:25,h:75},{x:325,y:250,w:25,h:75},{x:175,y:300,w:125,h:25},{x:225,y:325,w:25,h:50},{x:50,y:350,w:50,h:25},{x:75,y:350,w:25,h:75},{x:125,y:350,w:75,h:25},{x:275,y:350,w:75,h:25},{x:375,y:350,w:50,h:25},{x:375,y:350,w:25,h:75},{x:25,y:400,w:25,h:25},{x:125,y:400,w:25,h:75},{x:50,y:450,w:150,h:25},{x:275,y:450,w:150,h:25},{x:325,y:400,w:25,h:50},{x:425,y:400,w:25,h:25},{x:175,y:400,w:125,h:25},{x:225,y:425,w:25,h:50},{x:450,y:0,w:50,h:((canvas.height/2)-25)},{x:450,y:(canvas.height/2),w:50,h:((canvas.height/2))}];
function gen(){
for(var i=0;i<b.length;i++){
ctx.fillStyle="blue"
ctx.fillRect(b[i].x,b[i].y,b[i].w,b[i].h)
}
ctx.drawImage(ss,player.sx,player.sy,player.sw,player.sh,player.x,player.y,player.w,player.h)
}
function move(){
for(var i=0;i<b.length;i++){
//((a.x + a.width) < b.x)
if(left &&
player.x > b[i].x && (player.x + player.w) < (b[i].x + b[i].w) &&
player.y > b[i].y && (player.y + player.h) < (b[i].y + b[i].h)) {
// here you can tell that the user is colliding an object
player.x-=1
}
else {
}
}
}
function animate(){
ctx.save()
ctx.clearRect(0,0,canvas.width,canvas.width);
gen()
move()
ctx.restore();
}
var ani = setInterval(animate, 30)
}
window.addEventListener("load",function(){
init()
})
First, I can see a problem with the first part of your if condition:
left = true && ...
Should be
left === true && ...
Or even better
left && ...
Now for the collision part it usually is top-left or in the middle of the object
I'd suggest this top-left origin collision check:
if(left &&
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w) &&
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h) {
// here you can tell that the user is colliding an object
}
It checks several cases, this part
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w)
Will meet requirements if the player's x (with its width component) inside the occupied x range of the current object
The second part
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h)
Will meet requirements if the player's y (with its height component) is inside the occupied y range of the current object.
It will only execute the if statement if the condition is satisfied for both of the above cases.
You can tell if you should reposition the player on the left or on the right, by substracting the players x component to the object's x component, same goes for top or bottom with y component. The previous sentence is only valid if you move in a grid cell by cell.

Separating Rects Javascript

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.

Categories