I have a space ship in a canvas. It has velocities, ship.vx and ship.vy. When it's 30px away from the canvas borders I set ship.vx & ship.vy to 0 and move the background objects in ship's opposite direction. At this moment the ship is stuck at a point. That's all good. Now if I try to move it left-right(stuck at top/bottom) or top-down(stuck at left/right) it doesn't since it is stuck in the point where vx & vy are set to 0.
If i accelerate in it's opposite direction it takes like 5 seconds to pick it's velocity (around 2), so it's basically at the same point for 5 seconds.
I tried not to set vy to 0 when out of x-axis and vice-versa but the ship keeps moving slowly in the other axis.
So what i'm trying to achieve is the ship'll get stuck when it's 30 px from border, but if i try to move or accelerate in other 3 directions it'll pretend as if it's not stuck.
Any of you know any mechanisms?
Thanks.
function stuckShip(){
if(
(ship.x - ship.width < 0) ||
(ship.x + ship.width > w) ||
(ship.y + ship.height > h) ||
(ship.y - ship.height < 0))
{
ship.vx = 0;
ship.vy = 0;
}
}
function againAndAgain(){
var angle = ship.rotation;
var x = Math.cos(angle);
var y = Math.sin(angle);
var ax = x*thrust,
ay = y*thrust;
ship.vx += ax;
ship.vy += ay;
stuckShip();
ship.draw(context);
}
document.addEventListener('keydown', function(e){
switch(e.keyCode){
case 38:
thrust = 0.35;
break;
case 37:
ship.rotation -= 3;
break;
case 39:
ship.rotation += 3;
break;
}
}
Manipulating the velocity of an object based on it's position on the screen for display-purposes is never the best idea.
Often you use Parent-based systems, so you have one main-container and all objects (including the ship) are child to that container and move relatively to the main-container. Now you can update the container's position, if the ship's global position is in that 30px-band, to make it "lock" on the edge of the screen.
Ha ha, simple, just set ship's position to 31px from border,
if(ship.x <= 30){
ship.x = 30 + 1;
}
What it does is, when the ship is 30px from the left, it'll set ship.x to 31, so it'll never be stuck, just swinging 1px back and forth. i am not sure if it's a perfect solution, but it doesn't pull back the ship for 5 seconds.
Related
I'm learning a little bit of javascript and found that when you draw an object and want to set it to "bounce" off the border multiplying it (var *= -1) doesn't work. The object sticks to the border and stops moving.
In this example the ball reaches the top of the page and moves up and down repeatedly like it's "stuck" on the border:
// position of the ball
var y = 0;
// how far the ball moves every time
var speed = 2;
draw = function() {
background(127, 204, 255);
fill(66, 66, 66);
ellipse(200, y, 50, 50);
// move the ball
if (y > 375 || y < 25){
speed *= -1;
}
y = y + speed;
};
If the ball's y is either below 23 or greater than 377, inversing the speed will not get it into the accepted range again, and the direction will be inversed again, so it keeps jumping up and down. You could change the condition to:
if (y > 375 && speed > 0 || // if reaching upper broder and moving up or
y < 25 && speed < 0 // if reaching lower border and moving down
) speed *= -1;
If a circle hits a rectangle and needs to bounce, I need to calculate its new direction.
This is what I have
function tick() {
var dx = Math.cos(ball.direction * (Math.PI / 180))*ball_md.speed;
var dy = Math.sin(ball.direction * (Math.PI / 180))*ball_md.speed;
ball.x += dx;
ball.y -= dy;
drawGame(); // refresh board
//console.log(ball);
paddles.some(function(paddle) {
var circle={x:ball.x+ball_md.radius, y:ball.y+ball_md.radius, r:ball_md.radius};
var rect={x:paddle.x, y:paddle.y, w:game_md.paddle.width, h:game_md.paddle.height};
var hit = RectCircleColliding(circle, rect);
if (hit) {
if (Math.floor(ball.y) + ball_md.radius*2 <= paddle.y || Math.ceil(ball.y) >= paddle.y + game_md.paddle.height) { // hit on top or below paddle
ball.direction = 360 - ball.direction;
} else { // hit left or right side
ball.direction = 180 - ball.direction;
}
return true;
}
});
if (ball.y < 0 || ball.y+ball_md.radius*2 >= game_md.height) { // hit top or bottom wall
ball.direction = 360 - ball.direction;
}
if (ball.x < 0 || ball.x+ball_md.radius*2 >= game_md.width) { // hit left or right wall
ball.direction = 180 - ball.direction;
}
}
but it doesn't seem to always work. Does anyone know why?
Cases when it fails. In this case, it zigzags really fast on the paddle surface.
DEMO: https://jsfiddle.net/3ok2farw/2/
You need to treat the corner collision separately. When it hits the corner the velocity of the ball doesn't change perpendicular to either the horizontal or vertical axis, but along the line connecting the center of the ball and the corner. I'm adding a drawing to make it a bit clearer. If the collision is perfectly elastic (no energy loss), then the v_normal component gets replaced by its negative, -v_normal. If the collision is perfectly plastic (maximum energy loss), the exit velocity is just v_tangential. Hope this helps!
I am re-asking this question since I did not make myself clear in what I wanted in my last question.
Does anyone know how to do elastic collision or handle collision in Canvas using rectangles? Or can point me in the right direction?
I created a canvas that has multiple square and would like each square to deflect when they touch.
Here is a quick fiddle that I put together showing to black buffer canvases http://jsfiddle.net/claireC/Y7MFq/10/
line 39 is where I started the collision detection and line 59 is where I tried to execute it. I will have more than 3 squares moving around and want them to deflect if/when they touch each other
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d");
context.fillStyle = "#FFA500";
context.fillRect(0, 0, canvas.width, canvas.height);
var renderToCanvas = function (width, height, renderFunction) {
var buffer = document.createElement('canvas');
buffer.width = width;
buffer.height = height;
renderFunction(buffer.getContext('2d'));
return buffer;
};
var drawing = renderToCanvas(100, 100, function (ctx) {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
var drawing2 = renderToCanvas(100, 100, function (ctx) {
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
var x = 0,
y = 0,
x2 = 200,
y2 = 10,
vx = .80,
vy = .80,
vx2 = .80,
vy2 = .80;
function collides(rectA, rectB) {
return !(rectA.x + rectA.width < rectB.x2 ||
rectB.x2 + rectB.width < rectA.x ||
rectA.y + rectA.height < rectB.y2 ||
rectB.y2 + rectB.height < rectA.y);
};
function executeFrame() {
x+=vx;
y+=vy;
x2+=vx2;
y2+=vy2;
if( x < 0 || x > 579) vx = -vx;
if( y < 0 || y > 265) vy = -vy;
if( x2 < 0 || x2 > 579) vx2 = - vx2;
if( y2 < 0 || y2 > 233) vy2 = - vy2;
if(collides(drawing, drawing2)){
//move in different direction
};
context.fillStyle = "#FFA500";
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(drawing, x, y);
context.drawImage(drawing2, x2, y2);
requestAnimationFrame(executeFrame);
}
//start animation
executeFrame();
Rectangular collision detection
To do a rectangular collision detection can be more complicated than it perhaps looks.
It's not just about figuring out if the two rectangles intersects or overlaps, but we also need to know at what angle they collide and what direction they move in order to deflect them properly, ideally transfer "velocity" to each other (mass/energy) and so forth.
This method that I present here will do the following steps:
First do a simple intersect detection to find out if they collide at all.
If an intersection: calculate the angle between the two rectangle
Divide a set primary rectangle into four zones of a circle where zone 1 is right, zone 2 is bottom and so forth.
Depending on zone, check in what direction the rectangle is moving, if towards the other rectangle deflect it based on which zone was detected.
➔ Online demo
➔ Version with higher speed here
Detect intersection and calculate angle
The code for detecting the intersection and angle is as follows, where r1 and r2 are here objects with properties x, y, w and h.
function collides(r1, r2) {
/// classic intersection test
var hit = !(r1.x + r1.w < r2.x ||
r2.x + r2.w < r1.x ||
r1.y + r1.h < r2.y ||
r2.y + r2.h < r1.y);
/// if intersects, get angle between the two rects to determine hit zone
if (hit) {
/// calc angle
var dx = r2.x - r1.x;
var dy = r2.y - r1.y;
/// for simplicity convert radians to degree
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
if (angle < 0) angle += 360;
return angle;
} else
return null;
}
This function will return an angle or null which we then use to determine deflection in our loop (that is: the angle is used to determine the hit zone in our case). This is needed so that they bounce off in the correct direction.
Why hit zones?
With just a simple intersection test and deflection you can risk the boxes deflecting like the image on the right, which is not correct for a 2D scenario. You want the boxes to continue in the same direction of where there is no impact as in the left.
Determine collision zone and directions
Here is how we can determine which velocity vector to reverse (tip: if you want a more physical correct deflection you can let the rectangles "absorb" some of the other's velocity but I won't cover that here):
var angle = collides({x: x, y: y, w: 100, h: 100}, /// rect 1
{x: x2, y: y2, w: 100, h: 100}); /// rect 2
/// did we have an intersection?
if (angle !== null) {
/// if we're not already in a hit situation, create one
if (!hit) {
hit = true;
/// zone 1 - right
if ((angle >= 0 && angle < 45) || (angle > 315 && angle < 360)) {
/// if moving in + direction deflect rect 1 in x direction etc.
if (vx > 0) vx = -vx;
if (vx2 < 0) vx2 = -vx2;
} else if (angle >= 45 && angle < 135) { /// zone 2 - bottom
if (vy > 0) vy = -vy;
if (vy2 < 0) vy2 = -vy2;
} else if (angle >= 135 && angle < 225) { /// zone 3 - left
if (vx < 0) vx = -vx;
if (vx2 > 0) vx2 = -vx2;
} else { /// zone 4 - top
if (vy < 0) vy = -vy;
if (vy2 > 0) vy2 = -vy2;
}
}
} else
hit = false; /// reset hit when this hit is done (angle = null)
And that's pretty much it.
The hit flag is used so that when we get a hit we are marking the "situation" as a hit situation so we don't get internal deflections (which can happen at high speeds for example). As long as we get an angle after hit is set to true we are still in the same hit situation (in theory anyways). When we receive null we reset and are ready for a new hit situation.
Also worth to mention is that the primary rectangle here (whose side we check against) is the first one (the black in this case).
More than two rectangles
If you want to throw in more that two rectangle then I would suggest a different approach than used here when it comes to the rectangles themselves. I would recommend creating a rectangle object which is self-contained in regards to its position, size, color and also embeds methods to update velocity, direction and paint. The rectangle objects could be maintained by a host objects which performs the clearing and calls the objects' update method for example.
To detect collisions you could then iterate the array with these objects to find out which rectangle collided with the current being tested. It's important here that you "mark" (using a flag) a rectangle that has been tested as there will always be at least two in a collision and if you test A and then B you will end up reversing the effect of velocity change without using a flag to skip testing of the collision "partner" object per frame.
In conclusion
Note: there are special cases not covered here such as collision on exact corners, or where a rectangle is trapped between an edge and the other rectangle (you can use the hit flag mentioned above for the edge tests as well).
I have not optimized any of the code but tried to keep it as simple as I can to make it more understandable.
Hope this helps!
The answer is actually quite simple: swap the velocities of each block when they collide. That's it! Also for your collision test change RectA.x to just x, since they are normal variables given:
function collides(rectA, rectB) {
return !(x + rectA.width < x2 ||
x2 + rectB.width < x ||
y + rectA.height < y2 ||
y2 + rectB.height < y);
};
And swapping velocities:
if(collides(drawing, drawing2)){
var t = vx; var t2 = vy;
vx = vx2; vy = vy2;
vx2 = t; vy2 = t2;
};
And after those small changes we have working elastic collisions: http://jsfiddle.net/Y7MFq/11/
I am working on a maze game in HTML 5.
Here is the function which I use to draw an "X" on the canvas (using the touchpad, the user will navigate the X through the labyrinth).
dim = 8;
function rect(x,y,xdim){
ctx.beginPath();
ctx.moveTo(x - xdim, y - xdim);
ctx.lineTo(x + xdim, y + xdim);
ctx.moveTo(x + xdim, y - xdim);
ctx.lineTo(x - xdim, y + xdim);
ctx.strokeStyle = '#FF5000';
ctx.stroke();
}
The problem is in the checkcollision function, mainly in this piece of code:
ctx.getImageData(checkx, checky, 16, 16);
I am looking for an optimal way to check all the space that is taken up by the "X" on the canvas.
There are more comments in the code i used.
Issues:
1. Sometimes, the X goes a little passed the border
2. Sometimes, the X doesn't get close, letting a few pixels between it and the border.
function checkcollision(checkx, checky){
var collision = 0;
/*
User presses an arrow key
Calculate where that would move the square to.
Look at all of the pixels on the maze image that the square will cover
in its new position (our square is 15x15 so we look at 225 pixels)
Are any of the pixels black (the maze is white with black borders.
If a pixel is black that means the square would collide with a border)
YES: then do not move
NO: then move
*/
var imgd = ctx.getImageData(checkx, checky, 15, 15);
pix = imgd.data;
for (var i = 0; n = pix.length, i < n; i += 4){
if (pix[i] == 0) {
collision = 1;
} else if(pix[i] == 255 && pix[i + 1] == 0){
winner = "yes";
}
}
return collision;
}
I believe the getImageData gets a rectangle which starts from the x,y position in a corner.
So maybe there is a way to draw it using the checkx and checky as the coordinates for the center and retrieve a square 16 pixels wide
Wow, writing the question made it clear:
var imgd = ctx.getImageData(checkx - xdim, checky - xdim, 16, 16);
Ty guys
I am starting on a physics/particle simulator and I am having some trouble with collision detection:
http://mmhudson.com/physics.html
Im not so much looking for a code solution, but someone to explain the issue to me conceptually.
The way it works is I check to see if the particle is going to be inside/intersect with the object when it is next moved. If it is, the gravity multiplier is reversed so its direction is reversed.
The equation for movement I use is:
Next location = current speed + rate of gravity + current location
Where speed is the gravity multiplier
Hopefully someone has seen an issue like this before or is willing to check out the source of my page.
Any help at all is greatly appreciated
No conceptual explanation, but a bunch of random observations:
I'd recommend adding canvas width/height variables and comparing them against the particle position. Right now, your particles keep falling even if they drop off the canvas. Something like:
if( particles[i][1] > height )
particles.splice(i,1);
newPY < objects[k][2] + objects[k][3] + radius
is really weird. What are you getting from this? Adding the width and height of the objects and the particle radius? If you remove this part, particles will bounce off objects as long as they have "momentum".
As for momentum, I assume you want to figure out how to stop the particles from falling through the objects. Given current code, I'd do this: add a fifth variable to the particle, defaulting to the height of the canvas. Then, once you find out that you have an impact, save the impact position to the particle and after the loop, check if the particle is below that point. If so, reset it to that point. Dirty fix, but hey, it works. I've added the complete loop below that worked for me. To stop the objects from being wiped out by the clearRect method, maybe consider redrawing them.
You are checking for particles on top of the objects only, but I assume the "falling through" aspect is part of the bug you asked about, so not too important at the moment:
particles[i][1] < objects[k][1] + objects[k][3] + radius
Could be however, if you decided to play around and reduce gravity, so that particles would instead gain momentum and bounce against objects above.
As for your objCheck variable, you confuse the width for y in the last && part. It should be:
mY < objects[i][1] + objects[i][3] + radius
instead of
mY < objects[i][2] + objects[i][3] + radius
Right now, your objCheck is not working.
Also
for(var i=0; i < particles.length; i++) {
var clrRadius = 2*radius;
canvas.clearRect(particles[i][0]-radius, particles[i][1]-radius, clrRadius, clrRadius);
}
is better than
for(var i=0; i < particles.length; i++){
var clrRadius = radius + 4;
canvas.clearRect(particles[i][0]-(clrRadius/2), particles[i][1]-(clrRadius/2), clrRadius, clrRadius);
}
edit: Seems you changed the code, since I last checked it, so the above might no longer be relevant!
edit2: added particle stopping fix. Here's the complete gravity loop:
for(var i=0; i<particles.length; i++){
var clrRadius = 2*radius;
canvas.clearRect(particles[i][0]-radius, particles[i][1]-radius, clrRadius, clrRadius);
}
for(var i=0; i < particles.length; i++){
var newPY = particles[i][1] += particles[i][2] + particles[i][3];
for(var k=0; k<objects.length; k++){
if(
//particle
particles[i][0] > objects[k][0] - radius &&
particles[i][0] < objects[k][0] + objects[k][2] + radius &&
particles[i][1] > objects[k][1] - radius &&
particles[i][1] < objects[k][1] + objects[k][3] + radius //&&
){
//reverse gravity
particles[i][2] = particles[i][2] * -1;
particles[i][5] = objects[k][1] - radius;
}
}
particles[i][2] += particles[i][3]*weight;
particles[i][1] += particles[i][2];
if( particles[i][1] > particles[i][5] )
particles[i][1] = particles[i][5];
if( particles[i][1] > height )
particles.splice(i,1);
}
for(var i=0; i <particles.length; i++){
canvas.fillStyle = "#000";
canvas.beginPath();
canvas.arc(particles[i][0], particles[i][1], radius, 0, Math.PI*2, true);
canvas.closePath();
canvas.fill();
}
I wouldn't use a gravity multiplier.
Each object should look something like this:
var circle = {
x: 0, // x position
y: 0, // y position
dx: 0, // x velocity
dy: 0 // y velocity
}
To update the particle, multiply velocity (dx, dy) by some time interval and add this to the current position.
Every cycle, add some change in velocity as a result of gravity.
If you detect a collision change the velocity so the circles bounce off each other. An example of this would be:
// In a collision, simply reverse the direction of movement
// so the circles move away from each other.
function onCollision(circleA, circleB) {
circleA.dx *= -1;
circleA.dy *= -1;
circleB.dx *= -1;
circleB.dy *= -1;
}