Using circle math for solid collision - javascript

function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?

The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.

Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.

Related

Canvas circle collision, how to work out where circles should move to once collided?

I am having a go at building a game in html canvas. It's a Air Hockey game and I have got pretty far though it. There are three circles in the game, the disc which is hit and the two controllers(used to hit the disc/circle).
I've got the disc rebounding off the walls and have a function to detect when the disc has collided with a controller. The bit I am struggling with is when the two circle's collide, the controller should stay still and the disc should move away in the correct direction. I've read a bunch of article's but still can't get it right.
Here's a Codepen link my progress so far. You can see that the puck rebounds off the controller but not in the correct direction. You'll also see if the puck comes from behind the controller it goes through it.
http://codepen.io/allanpope/pen/a01ddb29cbdecef58197c2e829993284?editors=001
I think what I am after is elastic collision but not sure on how to work it out. I've found this article but have been unable to get it working.
http://gamedevelopment.tutsplus.com/tutorials/when-worlds-collide-simulating-circle-circle-collisions--gamedev-769
Heres is my collision detection function. Self refer's to the disc and the controller[i] is the controller the disc hits.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance <= addedRadius) {
var newVelocityX = (self.velocityX * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
var newVelocityY = (self.velocityY * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
self.velocityX = newVelocityX;
self.velocityY = newVelocityY;
self.x = self.x + newVelocityX;
self.y = self.y + newVelocityY;
}
}
}
Updated
Deconstructed a circle collision demo & tried to implement their collision formula. This is it below, works for hitting the puck/disc forward & down but wont hit the back backwards or up for some reason.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance < addedRadius) {
var normalX = distanceX / distance,
normalY = distanceY / distance,
midpointX = (controllers[i].x + self.x) / 2,
midpointY = (controllers[i].y + self.y) / 2,
delta = ((controllers[i].velocityX - self.velocityX) * normalX) + ((controllers[i].velocityY - self.velocityY) * normalY),
deltaX = delta*normalX,
deltaY = delta*normalY;
// Rebound puck
self.x = midpointX + normalX * self.radius;
self.y = midpointY + normalY * self.radius;
self.velocityX += deltaX;
self.velocityY += deltaY;
// Accelerate once hit
self.accelerationX = 3;
self.accelerationY = 3;
}
}
}
I'm not great at these types of math problems, but it looks like you need to rotate your vectors around sine and cosine angles. I will point you at a working example and the source code that drives it. I did not derive this example.
I solved just the circle collision detection part of this problem recently, but one solution I came across includes code for establishing new vector directions. Ira Greenburg hosts his original source at processing.org. Ira further cites Keith Peter's Solution in Foundation Actionscript Animation: Making Things Move!
I copied Ira's code into Processing's Javascript mode then pushed it to Github Pages so you can see it before you try it.
The main issue with my code was the user controller was attached to the mouse. When a collision would happen, the function would run constantly because the circles where still touching due to the mouse position. I changed my code so the controller is controlled by the users keyboard.
I also asked for help on reddit & got some help with my collision code. Some good resources where linked.
(http://www.reddit.com/r/javascript/comments/3cjivi/having_a_go_at_building_an_air_hockey_game_stuck/)

Determine hit on vertical or horizontal wall on a rectangle

Currently i am working on bouncing a ball of a wall, in classic 2D-geometry.
The wall can be hit on both vertical and horizontal sides, and the result of the reflection depends on which side the ball hits.
I have tried some different solutions, but they just make me more confused.
How can i determine whether the ball is hitting a vertical or horizontal side of the wall?
PseudoCode for the overview:
iterate through each wall
if (collision between ball and wall)
determine if vertical/horizontal hit
calculate new velocity for ball
I use this code for collision detection and it works like a charm:
source: Circle-Rectangle collision detection (intersection)
var isCollision = function (_projectile) {
if(direction != undefined){
var circleDistance = {};
circleDistance.x = Math.abs(_projectile.getCenter().x - centerX);
circleDistance.y = Math.abs(_projectile.getCenter().y - centerY);
if (circleDistance.x > (width/2 + _projectile.getRadius())) { return false; }
if (circleDistance.y > (height/2 + _projectile.getRadius())) { return false; }
if (circleDistance.x <= (width/2)) { return true; }
if (circleDistance.y <= (height/2)) { return true; }
var cornerDistance_sq = square(circleDistance.x - width/2) + square(circleDistance.y - height/2);
return (cornerDistance_sq <= (square(_projectile.getRadius())));
}
return false;
};
var square = function(_value){
return _value * _value;
};
Thank you!
update after question update
Asuming the ball has a direction/velocity vector dx,dy and has a radius of r (ball.x, ball.y are the ball positions of the ball center) do sth like this (have used similar in a billiard game, it is basic geometry and physics):
if (ball.x+ball.dx+r > wallV.x) ball.dx = -ball.dx // ball bounces off the
vertical wall
if ( ball.y+ball.dy+r > wallH.y ) ball.dy = -ball.dy // ball bounces off horizontal wall
do similar for opposite walls if needed, since velocity vector changes, the new ball position after bouncing (or anyway) will be ball.x += ball.dx; ball.y += ball.dy;
Bonus if you add some friction factor (meaning the magnitudes of dx dy eventually fade away to zero, the ball eventually stops after following a path)
To solve properly a collision, you can't just test on X => if collision solve and return THEN test on y => if collision solve on y.
You have to check which axe (x OR y) collides first, and there's also the case when the ball hits two walls at the same time.
So you can't just reason with space : you have to deal with time, and compute an ETA -Estimated Time Arrival- for all walls, based on the ball speed (assuming walls are still).
pseudo code :
minETA = a too big number;
colliderList = [];
for (each wall in walls) {
thisWallETA = computeETA(ball, wall);
if (thisWallETA > minETA) continue;
if (thisWallETA < minETA) {
minETA = thisWallETA;
colliderList.length = 0;
colliderList.push(wall);
continue;
}
if (thisWallETA == minETA) {
colliderList.push(wall);
continue;
}
}
Then when the colliderList has only one item => solve on corresponding axe. If it has two items => solve on both axes.
Then increase the current time by minETA, and try again until the currentTime + minETA you found is > to thisCycleTime + thisCycleDt.
If you're interested, i might clarify some things up.
Good luck !

Incorrect angle, wrong side calculated

I need to calculate the angle between 3 points. For this, I do the following:
Grab the 3 points (previous, current and next, it's within a loop)
Calculate the distance between the points with Pythagoras
Calculate the angle using Math.acos
This seems to work fine for shapes without angels of over 180 degrees, however if a shape has such an corner it calculates the short-side. Here's an illustration to show what I mean (the red values are wrong):
This is the code that does the calculations:
// Pythagoras for calculating distance between two points (2D)
pointDistance = function (p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x)*(p1x - p2x) + (p1y - p2y)*(p1y - p2y));
};
// Get the distance between the previous, current and next points
// vprev, vcur and vnext are objects that look like this:
// { x:float, y:float, z:float }
lcn = pointDistance(vcur.x, vcur.z, vnext.x, vnext.z);
lnp = pointDistance(vnext.x, vnext.z, vprev.x, vprev.z);
lpc = pointDistance(vprev.x, vprev.z, vcur.x, vcur.z);
// Calculate and print the angle
Math.acos((lcn*lcn + lpc*lpc - lnp*lnp)/(2*lcn*lpc))*180/Math.PI
Is there something wrong in the code, did I forget to do something, or should it be done a completely different way?
HI there your math and calculations are perfect. Your running into the same problem most people do on calculators, which is orientation. What I would do is find out if the point lies to the left or right of the vector made by the first two points using this code, which I found from
Determine which side of a line a point lies
isLeft = function(ax,ay,bx,by,cx,cy){
return ((bx - ax)*(cy - ay) - (by - ay)*(cx - ax)) > 0;
}
Where ax and ay make up your first point bx by your second and cx cy your third.
if it is to the left just add 180 to your angle
I've got a working but not necessarily brief example of how this can work:
var point1x = 0, point1y = 0,
point2x = 10, point2y = 10,
point3x = 20, point3y = 10,
point4x = 10, point4y = 20;
var slope1 = Math.atan2(point2y-point1y,point2x-point1x)*180/Math.PI;
var slope2 = Math.atan2(point3y-point2y,point3x-point2x)*180/Math.PI;
var slope3 = Math.atan2(point4y-point3y,point4x-point3x)*180/Math.PI;
alert(slope1);
alert(slope2);
alert(slope3);
var Angle1 = slope1-slope2;
var Angle2 = slope2-slope3;
alert(180-Angle1);
alert(180-Angle2);
(see http://jsfiddle.net/ZUESt/1/)
To explain the multiple steps the slopeN variables are the slopes of the individual line segments. AngleN is the amount turned at each junction (ie point N+1). A positive angle is a right turn and a negative angle a left turn.
You can then subtract this angle from 180 to get the actual interior angle that you want.
It should be noted that this code can of course be compressed and that five lines are merely outputting variables to see what is going on. I'll let you worry about optimizing it for your own use with this being a proof of concept.
You need to check boundary conditions (apparently, if points are colinear) and apply the proper calculation to find the angle.
Also, a triangle can't have any (interior) angle greater than 180 degress. Sum of angle of triangle is 180 degrees.

Check if a point goes through a line

I am programming a game in HTML5 canvas. If a line touches any point of its own or opponents line, that line stops moving. The Function I wrote follows below.
function hasPoints(x, y) {
for (var i = 0; i < points.length; i++) {
if (Math.abs(points[i][0] - x) < 2 && Math.abs(points[i][1] - y) < 2) {
return true;
break;
}
}
return false;
}
//check if this point crosses other players' points
if (hasPoints(p1.x,p1.y) || canPlay==false) {
clearLoop();
}else{
context.fillStyle = "green";
context.fillRect(p1.x,p1.y, 5,5);
addPoints(p1.x,p1.y);
sendMyPoints();
loopTimer = setTimeout('drawLine()', 50);
}
It works most of the time. But in some special case(As you can see in the picture below) it simply fails to return false.
Could anyone help me to improve this function, so that it works flawless?
Do not use dots, use vectors. An array of vectors.
To find out if a point is in contact with a line (vector)
Just mount a triangle with two points and the point vector analysis, if the area of the triangle is near zero (sets a limit), then this contact.
y1 x1 1
y2 x2 1 = (y1*x2*1)+(x1*1*y3)+(1*y2*x3)-(x1*y2*1)-(y1*1*x3)-(1*x2*y3)
y3 x3 1
the result should be close to zero (0.012121 ...)
This a way to discovery if a line is align in direction of a target
http://en.wikipedia.org/wiki/Determinant
Use math's expression to vectors then you can check if the vectors cross thenselves

Simulate a physical 3d ball throw on a 2d js canvas from mouse click into the scene

I'd like to throw a ball (with an image) into a 2d scene and check it for a collision when it reached some distance. But I can't make it "fly" correctly. It seems like this has been asked like a million times, but with the more I find, the more confused I get..
Now I followed this answer but it seems, like the ball behaves very different than I expect. In fact, its moving to the top left of my canvas and becoming too little way too fast - ofcouse I could adjust this by setting vz to 0.01 or similar, but then I dont't see a ball at all...
This is my object (simplyfied) / Link to full source who is interested. Important parts are update() and render()
var ball = function(x,y) {
this.x = x;
this.y = y;
this.z = 0;
this.r = 0;
this.src = 'img/ball.png';
this.gravity = -0.097;
this.scaleX = 1;
this.scaleY = 1;
this.vx = 0;
this.vy = 3.0;
this.vz = 5.0;
this.isLoaded = false;
// update is called inside window.requestAnimationFrame game loop
this.update = function() {
if(this.isLoaded) {
// ball should fly 'into' the scene
this.x += this.vx;
this.y += this.vy;
this.z += this.vz;
// do more stuff like removing it when hit the ground or check for collision
//this.r += ?
this.vz += this.gravity;
}
};
// render is called inside window.requestAnimationFrame game loop after this.update()
this.render = function() {
if(this.isLoaded) {
var x = this.x / this.z;
var y = this.y / this.z;
this.scaleX = this.scaleX / this.z;
this.scaleY = this.scaleY / this.z;
var width = this.img.width * this.scaleX;
var height = this.img.height * this.scaleY;
canvasContext.drawImage(this.img, x, y, width, height);
}
};
// load image
var self = this;
this.img = new Image();
this.img.onLoad = function() {
self.isLoaded = true;
// update offset to spawn the ball in the middle of the click
self.x = this.width/2;
self.y = this.height/2;
// set radius for collision detection because the ball is round
self.r = this.x;
};
this.img.src = this.src;
}
I'm also wondering, which parametes for velocity should be apropriate when rendering the canvas with ~ 60fps using requestAnimationFrame, to have a "natural" flying animation
I'd appreciate it very much, if anyone could point me to the right direction (also with pseudocode explaining the logic ofcourse).
Thanks
I think the best way is to simulate the situation first within metric system.
speed = 30; // 30 meters per second or 108 km/hour -- quite fast ...
angle = 30 * pi/180; // 30 degree angle, moved to radians.
speed_x = speed * cos(angle);
speed_y = speed * sin(angle); // now you have initial direction vector
x_coord = 0;
y_coord = 0; // assuming quadrant 1 of traditional cartesian coordinate system
time_step = 1.0/60.0; // every frame...
// at most 100 meters and while not below ground
while (y_coord > 0 && x_coord < 100) {
x_coord += speed_x * time_step;
y_coord += speed_y * time_step;
speed_y -= 9.81 * time_step; // in one second the speed has changed 9.81m/s
// Final stage: ball shape, mass and viscosity of air causes a counter force
// that is proportional to the speed of the object. This is a funny part:
// just multiply each speed component separately by a factor (< 1.0)
// (You can calculate the actual factor by noticing that there is a limit for speed
// speed == (speed - 9.81 * time_step)*0.99, called _terminal velocity_
// if you know or guesstimate that, you don't need to remember _rho_,
// projected Area or any other terms for the counter force.
speed_x *= 0.99; speed_y *=0.99;
}
Now you'll have a time / position series, which start at 0,0 (you can calculate this with Excel or OpenOffice Calc)
speed_x speed_y position_x position_y time
25,9807687475 14,9999885096 0 0 0
25,72096106 14,6881236245 0,4286826843 0,2448020604 1 / 60
25,4637514494 14,3793773883 0,8530785418 0,4844583502 2 / 60
25,2091139349 14,0737186144 1,2732304407 0,7190203271
...
5,9296028059 -9,0687933774 33,0844238036 0,0565651137 147 / 60
5,8703067779 -9,1399704437 33,1822622499 -0,0957677271 148 / 60
From that sheet one can first estimate the distance of ball hitting ground and time.
They are 33,08 meters and 2.45 seconds (or 148 frames). By continuing the simulation in excel, one also notices that the terminal velocity will be ~58 km/h, which is not much.
Deciding that terminal velocity of 60 m/s or 216 km/h is suitable, a correct decay factor would be 0,9972824054451614.
Now the only remaining task is to decide how long (in meters) the screen will be and multiply the pos_x, pos_y with correct scaling factor. If screen of 1024 pixels would be 32 meters, then each pixel would correspond to 3.125 centimeters. Depending on the application, one may wish to "improve" the reality and make the ball much larger.
EDIT: Another thing is how to project this on 3D. I suggest you make the path generated by the former algorithm (or excel) as a visible object (consisting of line segments), which you will able to rotate & translate.
The origin of the bad behaviour you're seeing is the projection that you use, centered on (0,0), and more generally too simple to look nice.
You need a more complete projection with center, scale, ...
i use that one for adding a little 3d :
projectOnScreen : function(wx,wy,wz) {
var screenX = ... real X size of your canvas here ... ;
var screenY = ... real Y size of your canvas here ... ;
var scale = ... the scale you use between world / screen coordinates ...;
var ZOffset=3000; // the bigger, the less z has effet
var k =ZOffset; // coeficient to have projected point = point for z=0
var zScale =2.0; // the bigger, the more a change in Z will have effect
var worldCenterX=screenX/(2*scale);
var worldCenterY=screenY/(2*scale);
var sizeAt = ig.system.scale*k/(ZOffset+zScale*wz);
return {
x: screenX/2 + sizeAt * (wx-worldCenterX) ,
y: screenY/2 + sizeAt * (wy-worldCenterY) ,
sizeAt : sizeAt
}
}
Obviously you can optimize depending on your game. For instance if resolution and scale don't change you can compute some parameters once, out of that function.
sizeAt is the zoom factor (canvas.scale) you will have to apply to your images.
Edit : for your update/render code, as pointed out in the post of Aki Suihkonen, you need to use a 'dt', the time in between two updates. so if you change later the frame per second (fps) OR if you have a temporary slowdown in the game, you can change the dt and everything still behaves the same.
Equation becomes x+=vx*dt / ... / vx+=gravity*dt;
you should have the speed, and gravity computed relative to screen height, to have same behaviour whatever the screen size.
i would also use a negative z to start with. to have a bigger ball first.
Also i would separate concerns :
- handle loading of the image separatly. Your game should start after all necessary assets are loaded. Some free and tiny frameworks can do a lot for you. just one example : crafty.js, but there are a lot of good ones.
- adjustment relative to the click position and the image size should be done in the render, and x,y are just the mouse coordinates.
var currWidth = this.width *scaleAt, currHeight= this.height*scaleAt;
canvasContext.drawImage(this.img, x-currWidth/2, y-currHeight/2, currWidth, currHeight);
Or you can have the canvas to do the scale. bonus is that you can easily rotate this way :
ctx.save();
ctx.translate(x,y);
ctx.scale(scaleAt, scaleAt); // or scaleAt * worldToScreenScale if you have
// a scaling factor
// ctx.rotate(someAngle); // if you want...
ctx.drawImage(this.img, x-this.width/2, x-this.height/2);
ctx.restore();

Categories

Resources