How would I improve my collision blocking system in JavaScript? - javascript

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.

Related

Force directed layout for constrained rectangular shapes

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.

Collision detection in javascript

I am implementing a custom space invader like in HTML/javascript. So far everything works fine but collision detection seems to be a problem. After looking for a couple of solutions online, here is what I have so far.
My enemies are represented in an array like this:
function Logo(I){
I = I || {};
I.sprite = new Image();
I.active = true;
I.width = 25;
I.height = 25;
I.explode = function(){
this.active = false;
}
I.draw = function(){
context.drawImage(I.sprite,this.x,this.y);
}
I.setRes = function(name){
this.sprite.src = name;
}
return I;
}
which is populated like this:
var logoArray = [];
for(i=0;i<logoData.length;i++){
logoArray.push(Logo({
x: logoData[i].x,
y: logoData[i].y
}));
logoArray[i].setRes("./graphics/logo_slices/logo_" + logoData[i].name + ".png");
console.log(logoArray[i].sprite.src);
}
The collision are handled like this (enemy.explode do a this.active = false):
function handleCollision(){
playerBullets.forEach(function(bullet) {
logoArray.forEach(function(enemy) {
if (isCollide(bullet, enemy)) {
enemy.explode();
bullet.active = false;
}
});
});
}
function isCollide(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}
The problem is that it makes inactive everything that is to the left of the impact point.
I understand that it is quite hard to depict my problem so happy to clarify.
The draw function filters to draw only the active elements of the array:
logoArray.forEach(function(logo_slice){
logo_slice.draw();
});
Thanks for any help you can give!
Regarding your collision logic, i would take the opposite approach:
Take the 4 cases that define a not collide and negate them.
being above, being below, being besides to left / right. not being either of these 4 makes it necessarily a collision.
Just as i guess this is the reason.
The rest of your "engine" looks reasonable and should work.
collide =
!(a.x + a.width < b.x ||
a.x > b.x + b.width ||
a.y + a.height < b.y||
a.y > b.y + b.height )
Additionally you could define a radius per entity that participates in the collisions and use a intersection via the radius. Of course then you need the center of the entities for this to work.
Edit:
For more details and elaborate example of different Collision Detection approaches in JS, see
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection.

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.

canvas: ball inside a big circle should deflect

I have a small circle which is inside a bigger circle. The small Circle is flying around, and if the small circle comes to the border of the big circle it should collide. I have almost managed to do so, but it still does not work perfect. Sometimes the circle collides just before the border, and sometimes right after the border. This is my code:
if (!(Math.pow((xSmallCircle + radiusSmallCircle) - (xBigCircle), 2) + Math.pow((
ySmallCircle + radiusSmallCircle) - yBigCircle, 2) < Math.pow(radiusBigCircle + 10, 2))) {
xVelocity *= -1;
yVelocity *= -1;
} else if (!(Math.pow((xSmallCircle - radiusSmallCircle) - (xBigCircle), 2) + Math.pow((
ySmallCircle - radiusSmallCircle) - yBigCircle, 2) < Math.pow(radiusBigCircle + 10, 2))) {
xVelocity *= -1;
yVelocity *= -1;
} else if (!(Math.pow((xSmallCircle + radiusSmallCircle) - (xBigCircle), 2) + Math.pow((
ySmallCircle - radiusSmallCircle) - yBigCircle, 2) < Math.pow(radiusBigCircle + 10, 2))) {
xVelocity *= -1;
yVelocity *= -1;
} else if (!(Math.pow((xSmallCircle - radiusSmallCircle) - (xBigCircle), 2) + Math.pow((
ySmallCircle + radiusSmallCircle) - yBigCircle, 2) < Math.pow(radiusBigCircle + 10, 2))) {
xVelocity *= -1;
yVelocity *= -1;
}
Any ideas why this does not work?
It's because you're only checking for collision at four points, which are actually all outside the inner circle. Try a simpler, and mathematically correct collision detection mechanism.
var sep = Math.sqrt(
Math.pow(xSmallCircle-xBigCircle, 2)
+ Math.pow(ySmallCircle-yBigCircle, 2)
);
if( sep + radiusSmallCircle + borderWidthSmallCircle >= radiusBigCircle ){
//You have a collision Here
}
The logic here is that if you calculate the length of the line segment between the centers of each circle, and add the small circle's radius, you'll get the radius of the smallest circle which is (a) concentric to the the outer circle, and (b) completely encircling the smaller circle. If that circle is larger or the same size as your outer circle, you have a collision.
If you still have inexact collisions, it's probably a rounding error.

Categories