Detecting collision between circle and square - javascript

I know this has been asked a lot, but personally, i couldn't find a suitable answer. Pretty much everywhere, the function looks something like:
function rccol(rect, circle){
var dx = Math.abs(circle.x - (rect.x + rect.width / 2));
var dy = Math.abs(circle.y - (rect.y + rect.height / 2));
if(dx > circle.radius + rect.width / 2) return false;
if(dy > circle.radius + rect.height / 2) return false;
if(dx <= rect.width) return true;
if(dy <= rect.height) return true;
var dx = dx - rect.width;
var dy = dy - rect.height
return((dx * dx + dy * dy) <= (circle.radius * circle.radius));
}
And that works perfectly. The issue is that I'm using it for collision detection in a game, where the circle is the player's collision box, and the square is let's say a wall. I need to be able to determine where the contact occurred so that i can actually prevent the "player" from going into the "wall" so the simple boolean that gets returned, doesn't really work in my use-case.

Having circle center coordinates (cx, cy), you can calculate squared distance from circle center to rectangle.
dx = Max(Abs(cx - rect.center.x) - rect.width / 2, 0)
dy = Max(Abs(cy - rect.center.y) - rect.height / 2, 0)
SquaredDistance = dx * dx + dy * dy
When circle is outside of rectangle initially, it is possible to remove Max call.
dx = Abs(cx - rect.center.x) - rect.width / 2
dy = Abs(cy - rect.center.y) - rect.height / 2
Also we can remove Abs for known initial position (note - upto moment of sign change!)
if cx >= rect.center.x:
dx = cx - rect.center.x - rect.width / 2
else:
dx = - cx + rect.center.x - rect.width / 2
To avoid a lot od different cases, we can virtually put player in the first quadrant relative to rectangle center and correspondingly change coordinates and velocity components
if cx0 < 0:
cx0 = - cx0
vx = -vx
if cy0 < 0:
cy0 = - cy0
vy = -vy
To register collision moment, you have to substitute coordinates with parametric equations using start point and velocity vector components (vx, vy)
cx = cx0 + vx * t
cy = cy0 + vy * t
and solve (quadratic) equation for t
SquaredDistance - Radius^2 = 0
(cx0 + vx * t - rect.center.x - rect.width/2)^2 +
(cy0 + vy * t - rect.center.y - rect.height/2)^2 -
Radius^2 = 0
Ater that you can get zero (no collision), one (touch event) or two roots (moment of collision and moment of exit).

Related

Move canvas image across circular path

I am currently working on a 2D javascript game and I am doing the movement mechanics right now. I want players to be able to move forward and backward, towards or away from the mouse, while also having the ability to strafe.
I got the mouse following down pretty well but I am stuck on how to implement the strafing. I need my player to move along a dynamic circular path that changes sizes depending on how far away the player is from the mouse.
ex: If the mouse was the red X, I want to move the player along the green circular path.
This path of course will be changing size based on how far away the player is from the mouse.
I am updating the players position by moving whenever the movement keys are pressed so I am really looking for an equation to move the player in the correct circular path that can be implemented in a "position updated every second" sort of way.
I know that for a circular path, the coordinates can be found by:
x = centerX * radius * Math.cos(theta);
y = centerY * radius * Math.sin(theta);
But I am having trouble implementing. Here is some of my framework, but I am afraid all the solutions I have tried haven't even gotten me close so I will not post the broken math I have already deleted
Player.prototype.update = function(delta){
this.playerCenter = [this.x+this.width/2, this.y+this.height/2];
let dX = (GAME.mouse.position.x - this.playerCenter[0]),
dY = (GAME.mouse.position.y - this.playerCenter[1]);
radius = Math.sqrt(dX * dX + dY * dY);
// Movement Forward
if(GAME.keyDown[87] && radius >= 50){
this.x += (dX / radius) * this.movementSpeed * delta;
this.y += (dY / radius) * this.movementSpeed * delta;
}
// Movement Backward
if(GAME.keyDown[83]){
this.x -= (dX / radius) * this.movementSpeed * delta;
this.y -= (dY / radius) * this.movementSpeed * delta;
}
// Strafe left
if(GAME.keyDown[65]){
}
// Strafe right
if(GAME.keyDown[68]){
}
}
You almost have the solution.
You need to go at 90 deg to the forward vector. To rotate a vector 90cw you swap the x,and y and negate the new x.
EG
dx = ?; // forward direction
dy = ?;
// rotate 90 clockwise
dx1 = -dy;
dy1 = dx;
Thus you can update your code as follows
var dX = (GAME.mouse.position.x - this.playerCenter[0]);
var dY = (GAME.mouse.position.y - this.playerCenter[1]);
var radius = Math.sqrt(dX * dX + dY * dY);
//normalise
if(radius > 0){
dX = (dX / radius) * this.movementSpeed * delta;
dY = (dY / radius) * this.movementSpeed * delta;;
}else{
dX = dY = 0; // too close need to set this or you will get NaN propagating through your position variables.
}
if(GAME.keyDown[87] && radius >= 50){ // Movement Forward
this.x += dX;
this.y += dY;
}
if(GAME.keyDown[83]){ // Movement Backward
this.x -= dX;
this.y -= dY;
}
if(GAME.keyDown[65]){ // Strafe left
this.x += -dY; // swap x and y negate new x to rotate vector 90
this.y += dX;
}
if(GAME.keyDown[68]){ // Strafe right
this.x -= -dY; // swap x and y negate new x to rotate vector 90
this.y -= dX;
}

Arcing coords from A to B

What I am aiming to do is arc the position of a circle towards the position of the mouse cursor, this all being relative to the world viewed through the canvas. To keep a handle on the speed at which the circle moves I decided to make a boundary larger than the circle, if the mouse is outside the boundary then the "position" of the mouse is brought to the boundary so that when I arc towards the coords, if they arent super far from the position of the circle it doesnt move at crazy speeds. I have this working and this is the code which does it:
dx = Game.controls.mouseX - (this.x - xView); // get the distance between the x coords
dy = Game.controls.mouseY - (this.y - yView); // get the distance between the y coords
radii = this.radius + 1; // +1 because the "radius" of the mouse is 1
if((dx * dx) + (dy * dy) > radii * radii) // is the mouse not over the player?
{
if((dx * dx) + (dy * dy) < 301 * 301)
{
this.x += ((Game.controls.mouseX - (this.x - xView)) * 2 / (this.mass)) + step;
this.y += ((Game.controls.mouseY - (this.y - yView)) * 2 / (this.mass)) + step;
}
else
{
mx = Game.controls.mouseX;
my = Game.controls.mouseY;
do
{
dx = mx - (this.x - xView);
dy = my - (this.y - yView);
mx += (((this.x - xView) - mx) * 2 / (this.mass)) + step;
my += (((this.y - yView) - my) * 2 / (this.mass)) + step;
} while((dx * dx) + (dy * dy) > 301 * 301)
this.x += ((mx - (this.x - xView)) * 2 / (this.mass)) + step;
this.y += ((my - (this.y - yView)) * 2 / (this.mass)) + step;
}
}
The magic for 'outside the boundary' lies withing the do while. This is the best fix I could come up with and I cant see this as being an elegant or fast solution and am wondering what the proper course of action should be.
Im no artist but hopefully this image helps to illustrate what I am trying to achieve. The Black dot is the mouse pos, the black circle is the circle and the red circle is the boundary I have specified. I want to get the coords marked by the X.
Your question is a special case of Circle line-segment collision detection algorithm?, in this case with B and C being the same points, so you can use your center point for both of them.
That solution is given in C, but it translates to JavaScript very easily, just replace float with var, use Math.sqrt() and so on...
Oh, and then there is a JvaScript version here: Calculate the point of intersection of circle and line through the center, that's more appropriate :-)
If the black circle is in the center of the red circle and you have the radius of the red circle
// c is circle center
// mouse is the mouse position. Should have properties x,y
// radius is the circle radius;
// returns the point on the line where the circle intercepts it else it returns undefined.
function findX(c, mouse, radius)
var v = {};
// get the vector to the mouse
v.x = mouse.x - c.x;
v.y = mouse.y - c.y;
var scale = radius / Math.hypot(v.x,v.y);
if(scale < 1){ // is it outside the circle
return {
x : c.x + v.x * scale,
y : c.y + v.y * scale
};
}
return;
}
And if the the line start is not the center then a general purpose line circle intercept function will solve the problem. If the line starts inside the circle the function will return just one point. If the line is not long enough it will return an empty array..
// p1,p2 are the start and end points of a line
// returns an array empty if no points found or one or two points depending on the number of intercepts found
// If two points found the first point in the array is the point closest to the line start (p1)
function circleLineIntercept(circle,radius,p1,p2){
var v1 = {};
var v2 = {};
var ret = [];
var u1,u2,b,c,d;
// line as vector
v1.x = p2.x - p1.x;
v1.y = p2.y - p1.y;
// vector to circle center
v2.x = p1.x - circle.x;
v2.y = p1.y - circle.y;
// dot of line and circle
b = (v1.x * v2.x + v1.y * v2.y) * -2;
// length of line squared * 2
c = 2 * (v1.x * v1.x + v1.y * v1.y);
// some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line.
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius));
// will give a NaN if no solution
if(isNaN(d)){ // no intercept
return ret;
}
// get the unit distance of each intercept to the line
u1 = (b - d) / c;
u2 = (b + d) / c;
// check the intercept is on the line segment
if(u1 <= 1 && u1 >= 0){
ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 });
}
// check the intercept is on the line segment
if(u2 <= 1 && u2 >= 0){
ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2});
}
return ret;
}

Calculate angle of triangle with sin or cos

I'm trying to calculate an angle based on triangle sides, preferably with sin.
The first 2 are helper functions getDistance and getPointsDifference
I have these functions:
var getDistance = function(p1, p2){
var dx = p1.x - p2.x, dy = p1.y - p2.y;
return Math.sqrt(dx*dx + dy*dy);
}
var getPointsDifference = function(p1, p2){
return {
x: -1 * (p1.x - p2.x),
y: (p1.y - p2.y)
}
}
and finaly:
var getMenuChoice = function(cx,cy, x, y){
var distance = getDistance({x:cx,y:cy}, {x:x,y:y});
if (distance <= 100) {
console.log(1)
} else {
console.log(2)
}
var diff = getPointsDifference({x:cx,y:cy}, {x:x,y:y});
var a = Math.sin(diff.y/distance)
console.log("asdf:", a)
}
Could someone please show me what am I doing wrong? I would like to calculate the result in degrees.
update
I detect a lick on the screen which gives me a x,y, and then I subtract those x,y from cx and cy which are the center of the screen
This is called the angle (or direction) of the vector from (or to, depends on what you need) point of click to the center of the screen. There is no need in calculation of the distance and arcsin of the angle (instead of yours sin) - you can just use Math.atan2(dy, dx);.
dy is change in y (y2 - y1) and dx is change in x (x2 - x1) between those two points. You can use a regular Math.atan(dy / dx), but then you must be sure that you are not dividing by zero and have to take into account the signs of dy and dx to have answer in the correct quadrant. Math.atan2 will do it all for you. And the picture below is just a reminder.
And yes, the answer will be in radians, as it was mentioned in comments. Conversion is simple degrees = radians * (180 / Math.PI);

HTML5 canvas viewport for rpg game map not generating

I'm working my way through some online examples and trying to get my head around how a HTML5 game may be put together.
I've got my head around the basics, such as the game loop, and separating the update and render logic.
I'm at the stage where I can generate a map from a tileset, and render a character who can walk around and have its sprite animate as it moves.
What I've struggling with is the way that the viewport follows the character as he moves. It's mostly working, the camera will follow - however, it has a very strange effect on the map that is generated.
The code so far is pretty long, but I've created a JSFiddle of the current way it works, and it illustrates the problems I'm having. The character can be moved with the arrow keys.
jsFiddle of the current issue
jsFiddle of the code
Incidentally, I've noticed if the character walks into the top left corner, it seems to be OK.
The code that is generating the map is:
tileSize = game.currentMap.tileset.tileWidth;
if (player.x >= (game.viewport.x / 2)) r = Math.floor(player.x - ((map.width * map.tileset.tileWidth) / 2))
for (r = Math.floor(game.viewport.x / map.tileset.tileWidth); r < Math.floor(game.viewport.x / map.tileset.tileWidth) + canvas.width / map.tileset.tileWidth + 1; r++) {
for (c = Math.floor(game.viewport.y / map.tileset.tileHeight); c < Math.floor(game.viewport.y / map.tileset.tileHeight) + canvas.height / map.tileset.tileHeight + 1; c++) {
var tile = ground[r][c];
var tileRow = (tile / game.currentMap.tileset.tilesInImgRow) | 0; // Bitwise OR operation
var tileCol = (tile % game.currentMap.tileset.tilesInImgRow) | 0;
ctx.drawImage(
game.currentMap.tileset.image, (tileCol * tileSize), (tileRow * tileSize),
tileSize,
tileSize, (c * tileSize) - game.viewport.x, (r * tileSize) - game.viewport.y,
tileSize,
tileSize);
tile = layer1[r][c];
tileRow = (tile / game.currentMap.tileset.tilesInImgRow) | 0;
tileCol = (tile % game.currentMap.tileset.tilesInImgRow) | 0;
ctx.drawImage(
game.currentMap.tileset.image, (tileCol * tileSize), (tileRow * tileSize),
tileSize,
tileSize, (c * tileSize) - game.viewport.x, (r * tileSize) - game.viewport.y,
tileSize,
tileSize);
}
}
However, as I'm studying the code from other resources - I'm a bit stuck as to whether the issue is being caused by this function or the function which generates and updates the viewport.
Any suggestions?
Update
The code that adjusts the viewport so that if the character is near the edge of the map, it no longer remains in the center of the viewport:
if (game.viewport.x <= 0) dX = player.x;
else if (game.viewport.x >= game.currentMap.tileset.tileWidth * game.currentMap.width - canvas.width) dX = player.x - game.viewport.x;
else dX = Math.round(canvas.width / 2 - player.width / 2);
if (game.viewport.y <= 0) dY = player.y;
else if (game.viewport.y >= game.currentMap.tileset.tileHeight * game.currentMap.height - canvas.height) dY = player.y - game.viewport.y;
else dY = Math.round(canvas.height / 2 - player.height / 2);
And the code that updates the viewport when the character is moved:
if (player.x + player.width / 2 < canvas.width / 2) {
game.viewport.x = 0;
} else if (player.x + canvas.width / 2 + player.width / 2 >= map.tileset.tileWidth * map.width) {
game.viewport.x = map.tileset.tileWidth * map.width - canvas.width;
} else {
game.viewport.x = Math.floor(player.x - (canvas.width / 2 - player.width / 2));
}
if (player.y + player.height / 2 < canvas.height / 2) {
game.viewport.y = 0;
} else if (player.y + canvas.height / 2 + player.height / 2 >= map.tileset.tileHeight * map.height) {
game.viewport.y = map.tileset.tileHeight * map.height - canvas.height;
} else {
game.viewport.y = Math.floor(player.y - (canvas.height / 2 - player.height / 2));
}
The viewport is initially set using the following:
var game = {
images: 0,
imagesLoaded: 0,
backgroundColor: '#000',
viewport: {
x: Math.floor(player.x - (canvas.width / 2 - playerSpriteSize / 2)),
y: Math.floor(player.y - (canvas.height / 2 - playerSpriteSize / 2))
},
currentMap: map,
fps: 0,
lastfps: 0,
fpsTimer: 0
}
Well, after playing with the code for a few days and many cups of coffee, I've managed to solve my own problem which I thought I would share for anyone else having similar issues.
The code to update the viewport was fine, the problem was the initial values assigned to the viewport and the way that the map x and y tiles were being iterated over.
In the game variable, I changed the viewport to be set to:
viewport: {
x: Math.floor(player.x - (canvas.width / 2)),
y: Math.floor(player.y - (canvas.height / 2))
},
And then in the drawMap function:
for (r = 0; r < map.rowTileCount; r++) {
for (c = 0; c < map.colTileCount; c++) {
This way, we are always generating the full map (which may be unnecessarily using extra resources, but we can always come back and look at this again later) and then we draw just a clipped part of this to the canvas which is exactly what I wanted.
http://jsfiddle.net/zdMSx/6/

Javascript: follow cursor but stay within a circle as opposed to rectangle div

I'm trying to get the jsfiddle sample below to make the yellow circle div follow the mouse but be constraint to a circle as opposed to a square (div).
http://jsfiddle.net/fhmkf/
The JS code looks like so:
var mouseX = 0, mouseY = 0, limitX = 150-15, limitY = 150-15;
$(window).mousemove(function(e){
mouseX = Math.min(e.pageX, limitX);
mouseY = Math.min(e.pageY, limitY);
});
// cache the selector
var follower = $("#follower");
var xp = 0, yp = 0;
var loop = setInterval(function(){
// change 12 to alter damping higher is slower
xp += (mouseX - xp) / 12;
yp += (mouseY - yp) / 12;
follower.css({left:xp, top:yp});
}, 30);
I understand the structure and methodology of the code, just not too familiar with the syntax!
Where is the best place to update the limitX/Y and throw in the extra radius, distance variables?
You can do this by computing the distance away from the center of the circle (2D distances are computed with Math.sqrt(x * x + y * y)). Then check to see if the distance is greater than the radius of the circle you'd like to constrain to. If it is, scale down the distance to match your constraining radius.
Here's the snippet:
var mouseX = 0, mouseY = 0, limitX = 150-15, limitY = 150-15;
var centerX = limitX / 2, centerY = limitY / 2;
var radius = centerX;
$(window).mousemove(function(e) {
var diffX = e.pageX - centerX;
var diffY = e.pageY - centerY;
// Get the mouse distance from the center
var r = Math.sqrt(diffX * diffX + diffY * diffY);
if (r > radius) {
// Scale the distance down to length 1
diffX /= r;
diffY /= r;
// Scale back up to the radius
diffX *= radius;
diffY *= radius;
}
mouseX = centerX + diffX;
mouseY = centerY + diffY;
});
And here's the complete code: http://jsfiddle.net/fhmkf/

Categories