Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (499)
Games in Android Showcase (118)
games submitted by our members
Games in WIP (567)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Rotate Towards Point  (Read 2029 times)
0 Members and 1 Guest are viewing this topic.
Offline danielcmessias

Senior Newbie





« Posted 2013-01-21 18:44:45 »

Hi,

I expect a very simple answer, but beyond me. I've done some searching but can't find anything.

I want to rotate a sprite at a constant rate a tick so it will try to face another sprite.

I can calculate the angle between them, it's dealing with the rotation increment/decrement when the angle crosses the 360 degrees mark to 0 degrees, messes up my code and I just can't think through it.

Many thanks for any help!
Offline Best Username Ever

Junior Member





« Reply #1 - Posted 2013-01-21 19:02:48 »

angle = cos-1((a / |a|) dot (b / |b|))

That will give you an angle between -180 and 180. For the sake of brevity, I will assume you understand the vector math, but if not, feel free to ask. Using that number you can do something like this.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
if(Math.abs(angle) < turnSpeed)
{
  thing.setDirection(Math.atan(thing.x - target.x, thing.y - target.y);
}
else if(angle > 0)
{
  thing.rotate(-turnSpeed);
}
else
{
    thing.rotate(turnSpeed);
}
Offline Sammidysam
« Reply #2 - Posted 2013-01-21 19:19:34 »

You are using two arguments in Math.atan.  Might want to switch it to Math.atan2 (which is a better method by all means).
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Agro
« Reply #3 - Posted 2013-01-21 19:23:23 »

Math.atan2(y, x) is the correct function parameters, you used Math.atan which only takes 1 parameter.

Offline danielcmessias

Senior Newbie





« Reply #4 - Posted 2013-01-21 19:31:25 »

Thanks for the help, I'm relatively new to Vector Maths, is this code looking somewhat right?

Written in the update method of an entity, trying to point towards the player.

1  
2  
3  
4  
Vector2 p1 = new Vector2(x/Math.abs(x),y/Math.abs(y));
Vector2 p2 = new Vector2(player.x/Math.abs(player.x),player.y/Math.abs(player.y));
         
float angle = (float) Math.acos(Math.toRadians(p1.dot(p2)));
Offline Best Username Ever

Junior Member





« Reply #5 - Posted 2013-01-21 19:37:16 »

There are a number of things I did not correct. It should be sin and the cross product. A is the direction of thing. B is the vector from thing to target.
Offline Agro
« Reply #6 - Posted 2013-01-21 19:38:56 »

There are a number of things I did not correct. It should be sin and the cross product. A is the direction of thing. B is the vector from thing to target.

I don't think that's exactly correct. All you need is Math.atan2 or the dot product. Where did you get sin and cross product from?

Offline danielcmessias

Senior Newbie





« Reply #7 - Posted 2013-01-21 19:40:54 »

1  
PlayerAngle = (float) Math.toDegrees(Math.atan2(player.x-x, player.y - y));


This will give me the angle between -180 and 180, I'm not sure what the need for the dot product is?

Edit:
Sorry, you said 'or', my bad!
Offline danielcmessias

Senior Newbie





« Reply #8 - Posted 2013-01-21 19:48:54 »

Ok, so currently I am at this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
playerAngle = (float) Math.toDegrees(Math.atan2(player.x-x, player.y - y));
turnSpeed = 2;
                 
if(Math.abs(playerAngle) < turnSpeed)
   {
      rotation = playerAngle;
   }
   else if(playerAngle > 0)
   {
      rotation -= turnSpeed;
   }
   else
   {
      rotation += turnSpeed;
   }


At the moment it seems I am only accounting for the angle between two co-ordinates, and not the current rotation of the entity. It's integrating that part that I'm finding most difficult.
Offline Agro
« Reply #9 - Posted 2013-01-21 19:52:07 »

1  
playerAngle = (float) Math.toDegrees(Math.atan2(player.x-x, player.y - y));


This line is wrong. It should be

1  
playerAngle = (float) Math.toDegrees(Math.atan2(player.y-y, player.x - x));

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline danielcmessias

Senior Newbie





« Reply #10 - Posted 2013-01-21 19:59:52 »

1  
playerAngle = (float) Math.toDegrees(Math.atan2(player.x-x, player.y - y));


This line is wrong. It should be

1  
playerAngle = (float) Math.toDegrees(Math.atan2(player.y-y, player.x - x));


Oh yeah, cheers.

I'm still confused on how to get this fully working though. Merely using playerAngle won't account for the entities current rotation, it'll only give the angle between two points. The entity will just spin infinitely as playerAngle will never change so long as it is static, despite it rotating. How do I factor in it's rotation?

Thanks
Offline matheus23

JGO Kernel


Medals: 108
Projects: 3


You think about my Avatar right now!


« Reply #11 - Posted 2013-01-21 20:23:12 »

Just an offtopic question: Why the heck is it (y, x) and not the usual (x, y)?

See my:
    My development Blog:     | Or look at my RPG | Or simply my coding
http://matheusdev.tumblr.comRuins of Revenge  |      On Github
Offline danielcmessias

Senior Newbie





« Reply #12 - Posted 2013-01-21 21:48:03 »

Just an offtopic question: Why the heck is it (y, x) and not the usual (x, y)?

That's just the order the atan2 function in java's math library wants them.
Offline Best Username Ever

Junior Member





« Reply #13 - Posted 2013-01-21 22:08:29 »

Actually it is important that you use the right inverse function to get the right sign. Intuitively, you want to put yourself in the position of your character and facing the same direction. You turn clockwise if it is to your left, counter clockwise if it is on your right, or face the target directly if is right about in front of you. Atan will give you an absolute angle. The dot product will tell you if something is in front or behind you. To make the sign tell whether you are on the left or right, rotate the direction you are facing 90 degrees and use that with your dot product. In 2D, that is like taking the Z-component of the cross product.


I was thinking cross product because swapping the order for two vectors changes the outcome while dot product does not. That gives you the correct sign and I think the right angle if you use sin-1. I was thinking my answer was applicable in 2D or 3D, but I will have to look at the math to figure out what my train of thought really was. In 2D, you could subtract two absolute angles from each other and adjust the answer to get the right sign.

If all the numbers work out, you could do the same thing I wrote in one line.
thing.rotate(Math.max(-maxTurn, Math.min(maxTurn, angle)));
Offline Agro
« Reply #14 - Posted 2013-01-21 22:11:35 »

Even if you don't get the right sign, you can still do a few calculations to get the right one.

And I think the reasoning for y being first in the Math.atan2 function is because its sort of like slope:

sin(a) / cos(a) = tan(a)


Offline sixtyten

Junior Member


Projects: 1



« Reply #15 - Posted 2013-01-21 22:31:15 »

Actually it is important that you use the right inverse function to get the right sign. Intuitively, you want to put yourself in the position of your character and facing the same direction. You turn clockwise if it is to your left, counter clockwise if it is on your right, or face the target directly if is right about in front of you. Atan will give you an absolute angle. The dot product will tell you if something is in front or behind you. To make the sign tell whether you are on the left or right, rotate the direction you are facing 90 degrees and use that with your dot product. In 2D, that is like taking the Z-component of the cross product.


I was thinking cross product because swapping the order for two vectors changes the outcome while dot product does not. That gives you the correct sign and I think the right angle if you use sin-1. I was thinking my answer was applicable in 2D or 3D, but I will have to look at the math to figure out what my train of thought really was. In 2D, you could subtract two absolute angles from each other and adjust the answer to get the right sign.

If all the numbers work out, you could do the same thing I wrote in one line.
thing.rotate(Math.max(-maxTurn, Math.min(maxTurn, angle)));


You can use a cross product to do the rotation as well, just do:

thing.forward x (target.pos - thing.pos)

where thing.forward is the forward vector of the sprite to be rotated (which doesn't need to be normalised).

Then as you show on your diagram, -ve or +ve Z indicate which way to rotate. Then keep rotating until the abs of the cross-product is below a threshold.
Offline sixtyten

Junior Member


Projects: 1



« Reply #16 - Posted 2013-01-21 22:53:51 »

Thanks for the help, I'm relatively new to Vector Maths, is this code looking somewhat right?

Written in the update method of an entity, trying to point towards the player.

1  
2  
3  
4  
Vector2 p1 = new Vector2(x/Math.abs(x),y/Math.abs(y));
Vector2 p2 = new Vector2(player.x/Math.abs(player.x),player.y/Math.abs(player.y));
         
float angle = (float) Math.acos(Math.toRadians(p1.dot(p2)));


Remember if you use the dot or cross product, you should be doing it with vectors and not the positions as you are using here. So as I said above, you want to use the entity forward (direction) vector and entity->player vector.

e.g. with cross product

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
// Get vector from entity to player
double wx = player.x - x;
double wy = player.y - y;

// Entity forward vector
double vx = Math.cos(rotation);
double vy = Math.sin(rotation);

// Cross-product of the two vectors - sign of result signals if the
// player is in a clockwise or counter-clockwise direction
double cross = (wx * vy) - (wy * vx);

// Rotate entity towards player
if (Math.abs(cross) < turnSpeed) {
     rotation = Math.atan2(wy, wx);
}
else if (cross < 0) {
     rotation += turnSpeed;
}
else {
     rotation -= turnSpeed;
}


Those calculations are in radians, but hope you get the gist.

Edit: Actually one other thing you need to check with the cross product is that the entity is not facing directly away from the player (i.e. 180 degrees), as the cross will also be zero then. You can do that with a dot product.
Offline Best Username Ever

Junior Member





« Reply #17 - Posted 2013-01-21 23:06:21 »

You need to normalize w.
Offline sixtyten

Junior Member


Projects: 1



« Reply #18 - Posted 2013-01-21 23:07:13 »

You need to normalize w.

Not if you're only checking the sign
Offline Best Username Ever

Junior Member





« Reply #19 - Posted 2013-01-21 23:15:24 »


True, but you are also checking its magnitude on line 14.
Offline sixtyten

Junior Member


Projects: 1



« Reply #20 - Posted 2013-01-21 23:18:11 »

Ya, fair point. That line is all wrong anyway as I'm comparing an angle to the (non-normalised) sine of an angle!  Roll Eyes
Offline Best Username Ever

Junior Member





« Reply #21 - Posted 2013-01-22 00:39:30 »

Use the angle of the direction the character is facing (a1) and atan2 to get the angle of the vector from the character to the target (a2). Adjust the angle (a2 - a1) using:

1  
2  
while(angle <= -180.0) angle += 360.0;
while(angle > 180.0) angle -= 360.0;


(Use radians in the real code.)

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
if(Math.abs(angle) < turn)
{
  character.setAngle(a2);
}
else if(angle > 0)
{
  character.setAngle(a1 - turn);
}
else // No need to test for -180 deg.
{
  character.setAngle(a1 + turn);
}
Offline Agro
« Reply #22 - Posted 2013-01-22 00:42:41 »

If you do it correctly, I don't even think the whiles are needed.

1  
double angle = Math.toDegrees(Math.atan2(target.x - this.x, target.y - this.y));


That will return an angle between -180 and 180 with the angle starting on the II quadrant along the X-axis. You just have to add 180 to it and everything will be resolved to an angle resulting in a positive number. I just did it right now. Cheesy

Offline Best Username Ever

Junior Member





« Reply #23 - Posted 2013-01-22 02:51:30 »

You could get 180 - -180 = 360 or -180 - 180 = -360 or anything in between. You don't need while loops either, but it gets the point across for the general case.
Offline Roquen
« Reply #24 - Posted 2013-01-22 07:19:56 »

Using a complex number/vectors instead you have:  the sign of the cross product (orthogonal direction) tells you which direction is the minimal angular path.  A constant angular velocity (for fixed simulation timesteps) is a fixed complex number. Multiple the current rotation (identical to unit direction vector) performs the rotation.  Re cross product and if the sign has changed you have overshot the target direction..set to target.  No trig, inverse trig or any explicit angles.
Offline Best Username Ever

Junior Member





« Reply #25 - Posted 2013-01-23 00:47:16 »

If you do that, you will continuously overshoot the target when the angle between them is less than the angular velocity. I tried to solve the problem of all three cases of the if, else if, else block without using only multiplications. You could avoid trig if you stored cos(maxTurn) (because it is constant.) I could not do away with both square roots and inverse trig functions.
Offline Roquen
« Reply #26 - Posted 2013-01-23 06:32:24 »

If you do that, you will continuously overshoot the target when the angle between them is less than the angular velocity.
Who are you talking to?  I note that if the sign changes you've overshot.
Offline danielcmessias

Senior Newbie





« Reply #27 - Posted 2013-01-23 20:16:50 »

I'm really confused, sorry, but this is maths all beyond my ability. Could anybody clarify how to actually practically implement this, ideally with  minimal advanced vector manipulation (advanced for me)! Thanks
Offline sixtyten

Junior Member


Projects: 1



« Reply #28 - Posted 2013-01-23 21:00:54 »

Ok, so currently I am at this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
playerAngle = (float) Math.toDegrees(Math.atan2(player.x-x, player.y - y));
turnSpeed = 2;
                 
if(Math.abs(playerAngle) < turnSpeed)
   {
      rotation = playerAngle;
   }
   else if(playerAngle > 0)
   {
      rotation -= turnSpeed;
   }
   else
   {
      rotation += turnSpeed;
   }


At the moment it seems I am only accounting for the angle between two co-ordinates, and not the current rotation of the entity. It's integrating that part that I'm finding most difficult.

The simplest solution (in terms of being easy to understand mathematically) is probably this - just a small modification to your previous code:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
// NOTE: atan2 takes y first, then x!
playerAngle = (float) Math.toDegrees(Math.atan2(player.y - y, player.x - x));
turnAngle = playerAngle - rotation;

// Bound range of turn between -180 and +180 degrees
// Can also use while's instead of if's, if turnAngle ends up outside range of -360 to +360
if (turnAngle < -180) turnAngle += 360;
if (turnAngle > 180) turnAngle -= 360;

turnSpeed = 2;
                 
if(Math.abs(turnAngle) < turnSpeed)
   {
      rotation = playerAngle;
   }
   else if(turnAngle > 0)
   {
      rotation += turnSpeed;
   }
   else
   {
      rotation -= turnSpeed;
   }


Make sure you check your code carefully. There were some bugs there (y/x being swapped in atan2, decreasing rotation when the angle was > 0) which you can spot yourself if you take the time to look through.
Offline danielcmessias

Senior Newbie





« Reply #29 - Posted 2013-01-23 21:01:54 »

Solution

I've finally reached a perfect solution, although I can't say this code is as efficient as can be. The real problem ended up the different way rotations were being stored. For the entities rotation, it run counterclockwise 0-360. The result of atan2 gave me something running clockwise and -180-180. The key turned out to be converting to counterclockwise 0-360, after which it was a trivial solution, although code posted earlier on here helped out, particulary in ensuring there isn't jerky movement when the entity is pointing towards the ship out by only a fraction of a degree.

I'm not sure if this solution would apply to anyone else's code, but hopefully it could be of help to somebody. Either way, there is plenty of reading material on this post, and I'd like to thank everybody who contributed and helped solve my issue!

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
playerAngle = (float) (Math.toDegrees((MathUtils.atan2(player.y-y, player.x-x)))+270)%360;
shipRotation = Math.abs(rotation%360);
turnSpeed = 2;
angle = playerAngle-shipRotation;
         
   if(Math.abs(angle)<turnSpeed){
      rotation = playerAngle;
   }else if(angle<0){
      rotation -= turnSpeed;
   }else{
      rotation += turnSpeed;
   }
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Pippogeek (39 views)
2014-09-24 16:13:29

Pippogeek (30 views)
2014-09-24 16:12:22

Pippogeek (19 views)
2014-09-24 16:12:06

Grunnt (44 views)
2014-09-23 14:38:19

radar3301 (27 views)
2014-09-21 23:33:17

BurntPizza (62 views)
2014-09-21 02:42:18

BurntPizza (32 views)
2014-09-21 01:30:30

moogie (39 views)
2014-09-21 00:26:15

UprightPath (50 views)
2014-09-20 20:14:06

BurntPizza (54 views)
2014-09-19 03:14:18
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!