I need to turn a click location into a polar coordinate.
This is my current algorithm. Location is the location on the canvas of the click ({x:evt.clientX, y:evt.clientY}), center is the offset of the origin from 0,0. For example, if the circle is centered on 250, 250, center is {x:250, y:250}. Scale is the scale of the radius. For example, if the radius of a circle from the center would normally be 50 and the scale is .5, the radius becomes 25. (it's for zooming in/out)
this.getPolarLocation = function(location){
var unscaledFromCenter = {
x: location.x - center.x,
y: location.y - center.y
};
var angle = this.getAngleOnCircle(unscaledFromCenter);
var dist = Math.sqrt(unscaledFromCenter.x * unscaledFromCenter.x + unscaledFromCenter.y * unscaledFromCenter.y) * this.ds.scale;
return {
angle:angle,
dist:dist,
toString: function(){
return "Theta: ".concat(angle).concat("; dist: ").concat(dist);
}
};
}
this.getAngleOnCircle = function(location){
var x = location.x;
var y = location.y;
if(x == 0 && y > 0)
return Math.PI / 2;
if(x == 0 && y < 0)
return 3 * Math.PI / 2;
if(y == 0 && x > 0)
return 0;
if(y == 0 && x < 0)
return Math.PI;
var angle = Math.atan(y/x);
if(x > 0 && y > 0)
return angle;
if(x < 0)
return Math.PI + angle
return Math.PI * 2 + angle;
}
Screenshots of the issue. The left is what happens zoomed out (and is not supposed to happen). The right is zoomed in (scale >= 1), and is what is supposed to happen.
I'm under the impression that my center coordinates are being shifted slightly off. It seems to work fine for scale >= 1, but not for scale < 1
Source:
circos.html: http://pastie.org/private/cowsjz7mcihy8wtv4u4ag
circos.js: http://pastie.org/private/o9w3dwccmimalez9fropa
datasource.js: http://pastie.org/private/iko9bqq8eztbfh8xpvnoaw
Run in Firefox
So my question is: why doesn't this work?
For some reason, the program automagically works when I close firebug. It doesn't seem to work on Firefox 5, only the version I have (in the 3s somewhere). Either way, I'm scrapping the project for something more object oriented. There's no way the current algorithm could handle a genome. (which is exactly what I'm going to be mapping)
UPDATE:
I figured out the problem... I was measuring the distance from the top left of the page, not the top left of the canvas. Thus, when firebug was enabled, the screen was shifted, making the problems worse. The solution is the use canvas.offsetLeft and canvas.offsetTop to calculate the position on the canvas.
Related
In this example we use math functions to create a blurred copy of an image. The way we will blur the image is by scrambling pixels that are near each other.
We begin by creating a blank image and writing the loop to let us color each pixel in the image. For each pixel we will do one of two things: half the time, we will simply copy the pixel from the old picture into the new picture without changing anything. The other half of the time we will find a pixel nearby and copy that one instead.
Now we must figure out how to find a "nearby" pixel. We will define some value for how far away the new pixel will be (we used 10 pixels) and then we write a function that will give a (x,y) point that is a random amount between 0 and 10 pixels away in each direction. Before we use the new (x,y) point, we must check to be sure it is still a valid pixel position in the image. For example, we may be at a pixel that is on the very top of the image. Our random point generator tells us to go up by 3 pixels, but since we are on the top of the image (y = 0) we cannot very well go up by three pixels (y would be -3)! If the random number is too big (larger than the dimension -1) or too small (less than 0) then we will just use the closest number that is valid.
Once we have a valid pixel that is some amount away we use its red, green, and blue values as the new pixel's values.
function nearby(image, x, y) {
var newX = x + Math.random() * 10 - 5;
if (newX > image1.getWidth()) {
newX = newX - 5;
}
if (newX < 0) {
newX = newX + 5;
} else {
newX = newX;
}
var newY = y + Math.random() * 10 - 5;
if (newY > image1.getHeight()) {
newY = newY - 5;
}
if (newY < 0) {
newY = newY + 5;
} else {
newY = newY;
}
return image.getPixel(newX, newY);
}
function blur(image) {
for (var pixel of image.values()) {
x = pixel.getX();
y = pixel.getY();
var orgPixel = image.getPixel(x, y);
if (Math.random() < 0.5) {
var other = nearby(image, x, y);
output.setPixel(x, y, other);
} else {
output.setPixel(x, y, orgPixel);
}
}
return output;
}
var image1 = new SimpleImage("duvall.jpg");
var output = new SimpleImage(image1.getWidth(), image1.getHeight());
blur(image1);
print(output);
----i used one of the pictures in my file but any picture should do.
I'm creating a birds-eye view, 2D game in JS using no engine/library (more for the learning challenge). I've got the character movement working with WASD where the character will either go forward (W) or back (S) depending on their current direction (A turns left, D turns right), but when I output the angles (direction) during play-time I'm getting unexpected results.
For example, when a player is facing "up" and I press "W", the player moves up, and the direction that gets output is 90° - as expected. When facing "down" and I press "S", the player moves down, and the direction is 270° - as expected.
But, when facing left, and I press "W", the character does move left but the output direction is 0°, and when facing + moving right, it is 180° - the exact opposite of what I expect.
These are the functions that move my players:
// Turning
_resetAngle(angle) {
if (angle >= 360) { return 0 }
if (angle < 0) { return 359 }
return angle
}
updateDirection(player, keyboardInput) {
let currentDirection = player.direction
const turnedLeft = keyboardInput['a']
const turnedRight = keyboardInput['d']
if (turnedLeft) { currentDirection -= this.turnAngle }
if (turnedRight) { currentDirection += this.turnAngle }
player.setDirection(this._resetAngle(currentDirection))
}
//Moving
_calculateNewCoords(movingForward, entity) {
let newX
let newY
// please ignore the code duplication, just testing for now
if (movingForward) {
newX = entity.getX() - entity.speed * Math.cos(entity.direction * (Math.PI / 180))
newY = entity.getY() - entity.speed * Math.sin(entity.direction * (Math.PI / 180))
}
else {
newX = entity.getX() + entity.speed * Math.cos(entity.direction * (Math.PI / 180))
newY = entity.getY() + entity.speed * Math.sin(entity.direction * (Math.PI / 180))
}
return { newX, newY }
}
updateCoordinatesByKeyboard(entity, keyboardInput) {
const movingForward = keyboardInput['w']
const movingBackwards = keyboardInput['s']
if ((movingForward && movingBackwards) || !(movingForward || movingBackwards)) { return }
const { newX, newY } = this._calculateNewCoords(movingForward, entity)
if (this._canMove(entity, newX, newY)) { return entity.setXY(newX, newY) }
}
And this is the part that renders the players:
drawCharacter(character, image) {
const scale = this._getScale(character) // for a 'breathing' effect, makes character grow and shrink
this.context.setTransform(scale, 0, 0, scale, this.windowDimensions.width / 2, this.windowDimensions.height / 2)
this.context.rotate(character.direction * Math.PI / 180)
this.context.drawImage(image, -image.width / 2, -image.height / 2)
}
Results when printing player.direction:
Output: 90 (as expected)
Output: 180 (expect it to be 0)
Output: 270 (as expected)
Output: 0 (expect it to be 180)
Output: 135 (expect it to be 45)
Output: 315 (expect it to be 225)
Again, just to reiterate - the players move as expected (i.e. pressing WASD makes the player turn and move correctly) - but the output directions are unexpected, and I'd like to fix that because in the future I'd like to set NPCs at certain angles (i.e. facing 45°) and expect them to face that direction without having to calculate the 'mirror' direction.
Thanks in advance for any help!
I wouldn't say the values you get don't make sense - it's quite the reverse - as it's what I would expect. So it's more of a 'cosmetical' problem as it would be the same for your other game objects.
Nevertheless you can easily recalculate the output to your desired value by:
output = Math.abs((input + 90) % 360 - 270)
This will make:
90 -> 90
180 -> 0
270 -> 270
0 -> 180
135 -> 45
315 -> 225
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!
#Pointy:
As I said below, the size variable adjusts the size by a percentage. So this number could have, for example, a value of 1, or 1.5, or 2, etc. The default circle diameter is 100 pixels, and the actual diameter of the circle displayed is size*100. When checking for collision I use size*120 because I want to leave some extra room.
To my understanding, the CSS left and top properties positions the object in terms of its left and top bounds, respectively. Thus if I want to check collision on the right side of the object, I'd have to take the left boundary and add the diameter of the circle (which is given by size*100). When checking the collision on the left side, I take the left bound and subtract size*(1.2-1)*100, because I do not need to account for the diameter of the circle as I am starting from the left bound. The same thing applies to the y-axis.
I hope this was understandable, thanks for helping me out.
Original Post:
I have two circles on my screen, a pink one and a purple one. The positions of these circles (x,y) are randomly determined. Initially, only the purple circle is on the page. One second later, the pink circle will appear. I want to check if these two circles will overlap, and if so, reposition the pink circle.
My code:
if(document.getElementById("purple").style.left+(size*120) > x
&& document.getElementById("purple").style.left-(size*20) < x)
{
if(x + 200 <= rightBound) {
x = x+200;
}
else {
x = x-200;
}
}
else if(document.getElementById("purple").style.top+(size*120) > y
&& document.getElementById("purple").style.top-(size*20) < y)
{
if(y+200 <= bottomBound) {
y = y+200;
}
else {
y = y-200;
}
}
document.getElementById("pink").style.left = x+"px";
document.getElementById("pink").style.top = y+"px";
The size variable modifies the circle size by a percentage. The default circle size is 100 pixels, but in my code I specify 120 pixels to leave a bit of extra room.
The rightBound and bottomBound variables dictate the bounds in which the circle must be randomly positioned. My code checks if moving the circle 200 pixels to the right, or 200 pixels down would still position the circle within the bounds. If so, then this is done, otherwise the circle is moved 200 pixels to the left instead.
By the way, in case it is not clear x and y are the randomly generated values for the position of the pink circle.
With this code, however, I have still experienced instances where the pink and purple circle will overlap. I'm wondering what the problem is with my logic, as in my explanation and thinking I feel that everything should be working fine.
Why not just keep trying new pink circles until you get one that's right? Random numbers are cheap, after all:
http://jsfiddle.net/HVX7d/
while(distance(purple, pink) < 120) {
pink = {
x: int_rand(100, 500),
y: int_rand(100, 500)
};
}
Just use a loop to keep generating new positions for pink until you're sure there are no collisions, using Pythagorean to calculate how far apart the circles are.
Original Answer (incorrectly assumed 100 was a radius):
The issue is that you're specifying 120 in your boundary logic but only moving the circle by 200 when you detect it. That means there's a scenario where the circles originally don't overlap, but when you move them by only 200 you've created an overlap that didn't previously exist.
Change your instances of 200 to 240 and you shouldn't see any more issues.
For example: you have purple at 200, 200 and pink at 310, 200. That's enough to trigger your boundary logic because 310 - 200 is only 110, so you choose to move pink. Let's say you move it up because it's near the bottom boundary, so now it's at 110, 200 and you've created overlap.
I think this should do what you need:
function reposition_check(x, y, rightBound, bottomBound, size, elem) {
var c1_size = 120 * size;
var c2_size = 120 * size;
var c1_x = parseInt(elem.style.left.replace("px", "")) + (c1_size / 2); //center the purple x coordinate
var c1_y = parseInt(elem.style.top.replace("px", "")) + (c1_size / 2); //center the purple y coordinate
var c2_x = x + (c2_size / 2); //center the pink x coordinate
var c2_y = y + (c2_size / 2); //center the pink y coordinate
var d_x = c1_x - c2_x; //distance on the x plane
var d_y = c1_y - c2_y; //distance on the y plane
if (Math.sqrt((d_x * d_x) + (d_y * d_y)) < (c1_size / 2) + (c2_size / 2)) { //Pythagorean theorum to determine if overlapping
console.log("is overlapping");
if (Math.abs(d_x) < (c1_size / 2) + (c2_size / 2)) { //if true, the overlap is on the x plane
//if(x + (c2_size) + 200 <= rightBound && d_x > 0) {
if(d_x > 0) {
x = (x + 200 <= rightBound) ? x + 200 : (x - 200 - d_x);
}else{
x = (x + 200 <= rightBound) ? x - d_x + 200 : (x - 200);
}
return {x: x, y: y};
}
if (Math.abs(d_y) < (c1_size / 2) + (c2_size / 2)) { //if true, the overlap is on the y plane
if(d_y > 0) {
y = (y + 200 <= bottomBound) ? y + 200 : (y - 200 - d_y);
}else{
y = (y + 200 <= bottomBound) ? y - d_y + 200 : (y - 200);
}
return {x: x, y: y};
}
}
return {x: x, y: y};
}
//Here's how you would use it:
var purple = document.getElementById("purple");
var values = reposition_check(111, 1, 1000, 800, 1, purple); //x, y, rightBound, bottomBound, size, element
document.getElementById("pink").style.left = values.x + "px";
document.getElementById("pink").style.top = values.y + "px";
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/