I am implementing a rotating cannon on a tank.
The cannon of the NPC is following the player tank (auto-rotating).
I am calculating:
The angle of the vector tank -> enemy (radians)
The angle of the cannon (radians)
The difference between both (I tried with Math.min and Math.abs after
googling)
Tthe problem is that the jump from 0 degrees to 360 or from 360 to 0 makes the cannon "loose" the player-tank and the cannon rotates once all around.
Right now its done like this:
Vector2 Tank2Enemy = tank.getPosition().sub(closestEnemyTank.getPosition());
float angleTank2Enemy = Tank2Enemy.angleRad(); // this is the angle of the vector tank -> enemy in radians
float angleCannon = tank.cannon.getOwnAngle(); // this is the cannon-angle in radians
float diffAngle = Math.min(Math.abs(angleCannon - angleTank2Enemy), MathUtils.PI - Math.abs(angleCannon - angleTank2Enemy));
if (diffAngle > 0.01f || diffAngle < -0.01f) { // dead zone
if (diffAngle > 0.01f) {
direction = -1; // this goes to cannon.getBody().setAngularVelocity(cannonAngularVelocity);
}
else if (diffAngle < 0.01f) {
direction = 1; // this goes to cannon.getBody().setAngularVelocity(cannonAngularVelocity);
}
}
return direction;
A good practice is to have an angle always positive for example in order not to mess with different sign in angles. You could notice that an angle 270 will work in the same way as -90 degrees. Your problem might be solved by attaching a simple check like this:
while(angle >= 2*Math.PI) //angle in radians
angle -= 2*Math.PI;
while(angle < 0)
angle += 2*Math.PI;
Update:
I guess the problem lies in the way you calculate the diffAngle. Could you try the following?
float diffAngle = angleCannon - angleTank2Enemy; // or angleTank2Enemy - angleCannon, depending on the direction of angles in your game
double sign = Math.signum(diffAngle);
float diffAngle = sign >= 0 ? Math.min(diffAngle, 2*MathUtils.PI - diffAngle) : Math.max(diffAngle, -(2*MathUtils.PI - diffAngle));
ok i solved it like this:
if (angleTank2Enemy > angleCannon) {
diffAngle = angleTank2Enemy - angleCannon;
if (diffAngle > MathUtils.PI) {
rotDirRight = true;
}
else rotDirRight = false;
}
else {
diffAngle = angleCannon - angleTank2Enemy;
if (diffAngle > MathUtils.PI) {
rotDirRight = false;
}
else rotDirRight = true;
}
if ((diffAngle) > 0.05f) { // dead zone
if (rotDirRight) direction = 1; // this goes to cannon.getBody().setAngularVelocity(cannonAngularVelocity);
else direction = -1; // this goes to cannon.getBody().setAngularVelocity(cannonAngularVelocity);
}
return direction;
Related
I need to change Ball Direction after collision with another ball or with a edge of the window.
I managed to do something like that:
y += yMove;
x += xMove;
//if the ball moves to the right edge of the window, turn around.
if(x > width - size)
{
x = width - size;
xMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
}
And same for another edges.
I'm trying to use same method for changing direction of balls after they collide with each other, but it's just not working / it's weird. Can anyone help me?
When balls collide, make vector connecting ball centers (N) and normalize it (uN)
Components of velocities parallel to N (normal) are exchanged (due to impulse law)
Components of velocities perpendicular to N (tangential) remain the same
To get components in given local system, use scalar and cross product:
V1t = dot(V1, uN)
V2t = dot(V2, uN)
V1n = cross(V1, uN)
V2n = cross(V2, uN)
after collision
V1t' = V2t
V2t' = V1t
V1n' = V1n
V2n' = V2n
To return into global system (I did not checked signs thoroughly):
V1x = V1t` * uN.X + V2n` * uN.Y
V1y = -V1t` * uN.Y + V2n` * uN.X
(This is essentially dot and cross products again, but I expanded expressions to show different bases)
Note that this approach is like to ball-edge collision, when N is normal to the border and you reverse only one component of velocity vector.
For your BouncingBall class, you can have a method like flipDirection(), but you can have a finer directional control by splitting it into 2 methods which filps the direction of the ball vertically and horizontally.
class BouncingBall{
public void horizontalFlip(){
moveX *= -1;
}
public void verticalFlip(){
moveY *= -1;
}
//To have move control over each direction, you can have a method for each direction.
public void moveNorth(){
moveY = Math.abs(moveY) * -1;
}
public void moveSouth(){
moveY = Math.abs(moveY);
}
public void moveWest(){
moveX = Math.abs(moveX) * -1;
}
public void mpveEast(){
moveX = Math.abs(moveX);
}
}
Depending on how you want the ball to bounce off. In a simple bounce off, the balls can bounce towards 4 possible directions:
North West
North East
South West
South East
The direction of the ball to bounce off will be relative to the position of the ball it is colliding with and you do not want 2 collided balls which move in the same direction to switch direction just because they collided. Hence you need to check the positions of the 2 balls, and flipDirection() becomes insufficinet to achieve that.
if(b1.intersects(b2)){
if(b1.getX() < b2.getX()){ // b1 above b2
b1.moveNorth();
b2.moveSouth();
}
else{
b1.moveSouth();
b2.moveNorth();
}
if(b1.getY() < b2.getY()){ // b1 at left side of b2
b1.moveWest();
b2.moveEast();
}
else{
b1.moveEast();
b2.moveWest();
}
}
For example, to change direction when hitting the walls on the left and right:
if(ball.getPosX() <= 0 || ball.getPosX() >= PNL_WIDTH-Ball.SIZE)
ball.horizontalReverse();
Same logic for verticalReverse.
I'm currently working on a Top-Down-Shooter and having some issues with collision.
My world is made of tiles (64x64). The tiles and the entities are rectangles. The player moves with a speed of e.g 2.74 (and not in pixels for smoother movement). But when it comes to the collision between the player (an entity) and a wall i have some issues. To check if there is a collision i take the current position of my player and his movement speed to calculate where his next position would be and if there is any collision. But i check every pixel on the way, so i cant skip an obstacle even if the movement speed is very high. Let's just say the players current position is X:200 Y:200 and he moves 2.74 Pixels a tick in the x direction. My game now checks if there is any collision at X:201 Y:200, X:202 Y:200 or X:202.74 Y:200 and if not moves the player to that position. If I now try to move the player further in the x direction and there is a wall 0.26 Pixels away the player wont move and leave a tiny gap. I tried to calculate the distance between player and wall and add this amount to the players position but for that I need to know which side of the wall the player hits. Also I want the player to be able to move up and down when the wall he hits is in front of him and the other way around.
Here is my collision method (in Java):
public static boolean collision(float ex, float ey, int width, int height) { // ex, ey would be the next position of the player
if (ex < 0 || ex + width > worldWidth || ey < 0 || ey + height > worldHeight) return true; // checks if this position is in the world
int firstTileX = (int) (ex / Tile.TILE_SIZE); // calculates tiles he could possible collide width
int firstTileY = (int) (ey / Tile.TILE_SIZE);
int lastTileX = (int) ((ex + width - 1) / Tile.TILE_SIZE);
int lastTileY = (int) ((ey + height - 1) / Tile.TILE_SIZE);
for (int y = firstTileY; y <= lastTileY; y++) {
if (y < 0) continue; // checks for out of bounds
if (y >= worldTileHeight) break;
for (int x = firstTileX; x <= lastTileX; x++) {
if (x < 0) continue;
if (x >= worldTileWidth) break;
if (tiles[y][x].solid) return true; // if the tile is solid -> collision found
}
}
return false; // no collision found
}
And my movement method:
public void move(float xa, float ya) {
float nx, ny;
while (xa != 0 || ya != 0) {
nx = x;
ny = y;
if (xa != 0) {
if (Math.abs(xa) > 1) { // if the x-speed is greater than 1
nx = x + MathUtil.abs(xa); // returns -1 for negative numbers and 1 for positiv
xa -= MathUtil.abs(xa);
} else { // less than 1
nx = x + xa;
xa = 0;
}
}
if (ya != 0) { // same here
if (Math.abs(ya) > 1) {
ny = y + MathUtil.abs(ya);
ya -= MathUtil.abs(ya);
} else {
ny = y + ya;
ya = 0;
}
}
if (!Level.collision(nx, ny, width, height)) setPosition(nx, ny); // checks if there is an collision and sets the new position if not
else if (!Level.collision(nx, y, width, height)) x = nx; // if there was a collision check if the player can walk in x direction
else if (!Level.collision(x, ny, width, height)) y = ny; // or in y direction
}
}
My problem is the pretty much the same as CoderMusgrove's problem in his post (Pixel-perfect collision and doubles):
Summary & Question
I have a problem where if the speed of an entity isgreater thanthe distance from the tile it is going into, it will leave at least a pixel in between itself and the tile, and I really don't like this. What kind of algorithm could I use that will find the tiniest difference between the entity and the tile?
If you need any additional information, I will be glad to add it.
Thanks for your help!
Easily resolvable by changing your interpretation.
You are retaining a fractional position for the purpose of fine grained speed. Ignore the fraction for the purpose of collision detection and display (if you were to do sub-pixel rendering, do the collision on the subpixel rendering accurarcy level).
int screenX = (int) Math.round(objX);
int screenY = (int) Math.round(objY);
// rendering and collision detection based on rounded position
for example my current angle position is 170, I click on the object to rotate it to -170 degrees, here the problem is that after 178, 179, 180, the next number is -180, -179, -178 and so on...
even tough numerically 170 degrees is far from -170 degrees, but visually they look near, an object is rotating the longest way in order to reach that number, for example:
if(currentAngle < targetAngle)
{
currentAngle += 1;
}
if(currentAngle > targetAngle)
{
currentAngle -= 1;
}
this way I can reach the target angle, but again how to transpass this barrier between 180 and -180, maybe there are a formula for this?
update:
onclick() {
double angle = Math.atan2(dy, dx);
targetAngle = (int)Math.toDegrees(angle); //180 to //-179
}
onFrame() {
//here happens the animation
if(currentAngle < targetAngle)
{
currentAngle +=1;
}
if(currentAngle > targetAngle)
{
currentAngle -= 1;
}
}
now what if I'am currently on -179 angle degree, and I clicked on 160 angle degree, it should rotate to left to reach that angle as fast as posible, but in my case it is rotating to the right(which is longer), and thats because it is limited from -180 to 179, thus how to break the limits and go from 179 to -180, -181, -182...if you understand what I mean
1) my click handler:
onClick() {
double angle = Math.atan2(dy, dx);
angle = (int)Math.toDegrees(angle);
Log.d("test", "angler:" + angle);
if(angle < 0)
angle += 360;
}
so here I convert the degrees to positive using angle += 360, then:
2) my onFrame handler:
onFrame() {
if(currentAngle != angle)
{
if(angle < currentAngle)
{
currentAngle -= 5;
}
else
{
currentAngle += 5;
}
int diff = Math.abs(angle - currentAngle);
if(diff <= 5)
{
currentAngle = angle; // if its near we put it exact to that angle
}
invalidate(); //update the view
}
}
thats all I have
Note that I am assuming your angles are between 0 and 359, rather than having negatives.
There are two separate problems here.
Given a currentAngle and a targetAngle, how does one determine the direction of rotation which will result in completing the rotation in the shortest number of frames?
How do we handle the boundary condition where the angle crosses from 359 to 0/360?
Solution
Problem 1 is mostly addressed in #ellitron's answer, but you have to separate out the pieces a little bit to determine the direction to move your currentAngle. Problem 2 requires a mod by 360 which handles negative numbers after each update to currentAngle, and this answer gives us a nice trick for that.
if (currentAngle - targetAngle == 0) return;
if (Math.abs(currentAngle - targetAngle) < 180) {
// Rotate current directly towards target.
if (currentAngle < targetAngle) currentAngle++;
else currentAngle--;
} else {
// Rotate the other direction towards target.
if (currentAngle < targetAngle) currentAngle--;
else currentAngle++;
}
currentAngle = ((currentAngle % 360) + 360) % 360;
For future reference, here is how one might test this code outside of a rendering environment. We can simply pass in two command line arguments and determine from the output whether we rotated the right way or not.
public class Angle {
public static void main(String[] args) {
int currentAngle = Integer.parseInt(args[0]);
int targetAngle = Integer.parseInt(args[1]);
while (currentAngle - targetAngle != 0) {
if (Math.abs(currentAngle - targetAngle) < 180) {
// Rotate current directly towards target.
if (currentAngle < targetAngle) currentAngle++;
else currentAngle--;
} else {
// Rotate the other direction towards target.
if (currentAngle < targetAngle) currentAngle--;
else currentAngle++;
}
currentAngle = ((currentAngle % 360) + 360) % 360;
System.out.printf("CurrentAngle = %d, targetAngle = %d\n",
currentAngle, targetAngle);
}
}
}
As you have defined currentAngle in your question, its values in ascending order are:
0,1,2,...,180,-179,-178,...,0
Which means that for you, -179 is greater than 179, and therefore arithmetic comparison will not work for you. First you must convert these numbers to a range that looks like:
0,1,2,...,180,181,182,...,359
Which you can do with the following formula:
if(angle < 0)
angle += 360
Now you can find the difference between the two angles (say angle1 and angle2) like this:
abs(angle1 - angle2)
or if you want to cross over 0, then do this:
360 - abs(angle1 - angle2)
To give you the shortest distance between these two angles, you would take the minimum like this:
min(abs(angle1 - angle2), 360 - abs(angle1 - angle2))
I had the same problem for rotation animation. From 1 to -1 it goes through the large arc, with length 358 but the small arc is with length 2. So does the opposite. But imagine it gets values 0, 100, 200, 300, 0, 100, 200, 300, and so on... in order animation to be smooth it has to go through values 0, 100, 200, 300, 360, 460, 560, 660, .. so the angle will rise if it turns only clockwise.
Check out this algorithm:
class RotAngle
{
double oldAngle = 0; //old angle modulo 360
int rot = 0; //this is current spin count (times 360 degrees)
public double Rotate(double angle)
{
double currAngle = ((angle % 360) + 360) % 360;
//if you enter only positive angles (angle % 360) will do
//((angle % 360) + 360) % 360 is needed for negative angles
double diff_2 = 2 * (oldAngle - currAngle);
/* mathematically is equal to: old + 360 - current < current - old */
/* so closer is one rotation less */
if (diff_2 < -360) rot--;
/* opposite: 360 + current - old < old - current -> one rotation more is closer */
if (diff_2 > 360) rot++;
oldAngle = currAngle;
return rot * 360 + currAngle;
}
}
I want the player image to point towards the mouse cursor. I use this code to get the postion of the mouse cursor:
private int cursorX = MouseInfo.getPointerInfo().getLocation().x;
private int cursorY = MouseInfo.getPointerInfo().getLocation().y;
Note: The default player image points upwards
You'll have to use trigonometry in order to calculate the angle of rotation. For that you'll first need to obtain the location of the image and the cursor. I cannot tell you how to get the position for the image as this may vary. For this example (adapted from here), I'll assume imageX and imageY are the x and y positions of your image:
float xDistance = cursorX - imageX;
float yDistance = cursorY - imageY;
double rotationAngle = Math.toDegrees(Math.atan2(yDistance, xDistance));
To find the angle from a coordinate (0,0) to another coordinate (x,y), we can use the trigonometric function tan^-1(y/x).
Java's Math class specifies a static method atan2 which acts as a tan^-1 function (also known as "arctangent", hence "atan") and returns the angle in radians. (There is a method atan which takes one argument. See the linked Javadoc.)
In order to find the angle in degrees from the coordinate of your "player" to the coordinate of the mouse cursor, (I'll assume this "player" you make mention of has x and y coordinates), we need to do something like this:
double theta = Math.atan2(cursorY - player.getY(), cursorX - player.getX());
It is also of note that an angle of zero radians would indicate that the mouse is directly to the right of the player. You mention that the "default player image" points upwards; if you mean that before rotation, your image faces upward for the player, it would be more conventional to geometry and the Java implementation of atan2 to have your player face right "by default".
Though this was asked two years ago...
If you need the mouse to keep updating the mouse position in the window, see mouseMotionListener. The current you use to get the mouse position is relative to the whole screen. Just keep that in mind.
Otherwise, here is a method I use,
public double angleInRelation(int x1, int y1, int x2, int y2) {
// Point 1 in relation to point 2
Point point1 = new Point(x1, y1);
Point point2 = new Point(x2, y2);
int xdiff = Math.abs(point2.x - point1.x);
int ydiff = Math.abs(point2.y - point1.y);
double deg = 361;
if ( (point2.x > point1.x) && (point2.y < point1.y) ) {
// Quadrant 1
deg = -Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ( (point2.x > point1.x) && (point2.y > point1.y) ) {
// Quadrant 2
deg = Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ( (point2.x < point1.x) && (point2.y > point1.y) ) {
// Quadrant 3
deg = 90 + Math.toDegrees(Math.atan(Math.toRadians(xdiff) / Math.toRadians(ydiff)));
} else if ( (point2.x < point1.x) && (point2.y < point1.y) ) {
// Quadrant 4
deg = 180 + Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ((point2.x == point1.x) && (point2.y < point1.y)){
deg = -90;
} else if ((point2.x == point1.x) && (point2.y > point1.y)) {
deg = 90;
} else if ((point2.y == point1.y) && (point2.x > point1.x)) {
deg = 0;
} else if ((point2.y == point2.y) && (point2.x < point1.x)) {
deg = 180;
}
if (deg == 361) {
deg = 0;
}
return deg;
}
In words, you get the angle of each of the θs as shown in the picture below and check if x or y are 0 and make a special case for that.
The origin is the middle of the picture and each of the points (marked with a hand-drawn cross) is where the mouse position is.
I'm attempting to play with graphics using Java/Slick 2D. I'm trying to get my sprite to rotate to wherever the mouse is on the screen and then move accordingly.
I figured the best way to do this was to keep track of the angle the sprite is at since I have to multiply the cosine/sine of the angle by the move speed in order to get the sprite to go "forwards" even if it is, for instance facing 45 degrees in quadrant 3.
However, before I even worry about that, I'm having trouble even getting my sprite to rotate in the first place.
Preliminary console tests showed that this code worked, but when applied to the sprite, it just kind twitches. Anyone know what's wrong?
int mX = Mouse.getX();
int mY = HEIGHT - Mouse.getY();
int pX = sprite.x;
int pY = sprite.y;
int tempY, tempX;
double mAng, pAng = sprite.angle;
double angRotate = 0;
if (mX != pX) {
tempY = pY - mY;
tempX = mX - pX;
mAng = Math.toDegrees(Math.atan2(Math.abs((tempY)), Math.abs((tempX))));
if (mAng == 0 && mX <= pX)
mAng = 180;
}
else {
if (mY > pY)
mAng = 270;
else
mAng = 90;
}
// Calculations
if (mX < pX && mY < pY) { // If in Q2
mAng = 180 - mAng;
}
if (mX < pX && mY > pY) { // If in Q3
mAng = 180 + mAng;
}
if (mX > pX && mY > pY) { // If in Q4
mAng = 360 - mAng;
}
angRotate = mAng - pAng;
sprite.angle = mAng;
sprite.image.setRotation((float) angRotate);
Firstly, atan2 can get the correct angle for you - just remove Math.abs from the inputs, and you won't need your three four if statements that you use to correct the quadrants of the angle. (Though you have to get the subtractions the right way around)
Secondly, you're setting the sprite's rotation to mAng - pAng, which amounts to "old angle - new angle". So in reality you're setting the rotation to how much the angle changed since last time (which makes no sense for this purpose), and not the angle itself.
Combining these suggestions I'd recommend something like this:
mAng = Math.toDegrees(Math.atan2(mY - pY, mX - pX));
sprite.angle = mAng;
sprite.image.setRotation((float) mAng);