Vector problem

24 posts

Flag Post

I use this function to move units according to their current direction and move speed.

		public function Update(elapsedTime:Number):void
		{
			// calculate the current forward vector
            var forward:Vector2 = new Vector2(Math.sin(Rotation), -Math.cos(Rotation));
            var right:Vector2 = new Vector2(-forward.y, forward.x);

            // calculate the vector between the unit and the position that the unit is moving to
            var moveTargetVector:Vector2 = Vector2.Subtract(moveTarget, Location);
			
            // calculate the new forward vector with movement target vector
            if (moveTargetVector.length > moveSpeed * elapsedTime)
            {
                // change the direction
                var wantedForward:Vector2 = Vector2.Normalize(moveTargetVector);
                var angleDiff:Number = Math.acos(
                    Vector2.Dot(wantedForward, forward));

                var facing:Number = (Vector2.Dot(wantedForward, right) > 0) ?
                    1 : -1;
				
                if (angleDiff > 0.001)
                {
                    Rotation += Math.min(angleDiff, facing * elapsedTime *
                        turnSpeed);
                }

                // change velocity
                velocity = Vector2.Scale(wantedForward, moveSpeed);
            }
            else
            {
                moveTarget = Location;
                velocity = Vector2.Zero();
			}
			
			var moveVector:Vector2 = Vector2.Scale(velocity, elapsedTime);
			
			Location = Vector2.Add(Location, moveVector);
		}

When I test with this with my player unit and I try to turn in a counter-clockwise direction then the unit starts shaking like its going in two different directions with each frame. I think it’s something to do with the angleDiff, but I can’t find any problem with it. Help please!

 
Flag Post

This is the Vector2 class that I created.

package 
{
	import flash.geom.Point;
	
	/**
	 * Defines a 2 dimensional vector. Provides functions for 
	 * the manipulation of 2D vectors.
	 */
	final public class Vector2 extends Point
	{
		/**
		 * Constructs a new Vector2.
		 * @param	x The x component of the vector.
		 * @param	y The y component of the vector.
		 */
		public function Vector2(x:Number, y:Number)
		{
			super(x, y);
		}
		
		/**
		 * Gets a zero vector.
		 * @return A vector with a value of 0 for both components.
		 */
		public static function Zero():Vector2
		{
			return new Vector2(0, 0);
		}
		
		/**
		 * Adds two vectors.
		 * @param	vector1 The first vector.
		 * @param	vector2 The second vector.
		 * @return The sum of the vectors.
		 */
		public static function Add(vector1:Vector2, vector2:Vector2):Vector2
		{
			return new Vector2(vector1.x + vector2.x, vector1.y + vector2.y);
		}
		
		/**
		 * Subtracts two vectors.
		 * @param	vector1 The first vector.
		 * @param	vector2 The second vector.
		 * @return The difference between the vectors.
		 */
		public static function Subtract(vector1:Vector2, vector2:Vector2):Vector2
		{
			return new Vector2(vector1.x - vector2.x, vector1.y - vector2.y);
		}
		
		/**
		 * Scales a vector by a specified amount.
		 * @param	vector The vector to scale.
		 * @param	scale The amount to scale by.
		 * @return The scaled vector.
		 */
		public static function Scale(vector:Vector2, scale:Number):Vector2
		{
			return new Vector2(vector.x * scale, vector.y * scale);
		}
		
		/**
		 * Normalizes a vector to a unit vector.
		 * @param	vector The vector to normalize.
		 * @return The unit vector. A unit vector points in the same 
		 * direction but has a length of 1.
		 */
		public static function Normalize(vector:Vector2):Vector2
		{
			return new Vector2(vector.x / vector.length, vector.y / vector.length);
		}
		
		/**
		 * Calculates the dot product of two vectors.
		 * @param	vector1 The first vector.
		 * @param	vector2 The second vector.
		 * @return The dot product of the two vectors.
		 */
		public static function Dot(vector1:Vector2, vector2:Vector2):Number
		{
			return vector1.x * vector2.x + vector1.y * vector2.y;
		}
		
		/**
		 * Calculates the angle, in radians, between two vectors.
		 * @param	vector1 The first vector.
		 * @param	vector2 The second vector.
		 * @return The angle between the two vectors.
		 */
		public static function AngleBetween(vector1:Vector2, vector2:Vector2):Number
		{
			return Math.atan2(vector2.y - vector1.y, vector2.x - vector1.x);
		}
	}
}
 
Flag Post

I believe I’ve found your problem. OK, so this:

var angleDiff:Number = Math.acos(Vector2.Dot(wantedForward, forward));

returns a positive angle value in radians, between 0 and pi. But this:

Rotation += Math.min(angleDiff, facing * elapsedTime * turnSpeed);

expects a rotation value in degrees. So your problem is twofold. First you put facing (which can be negative) in your min call, and that’s going to bite you in the rear no matter what. Second, you didn’t convert angleDiff to degrees.

This:

Rotation += facing * Math.min(angleDiff * 180 / Math.PI, elapsedTime * turnSpeed);

should solve both problems.

 
Flag Post

Yeah.

 
Flag Post

You can turn but the unit starts shaking like its turning both ways (but as you can see the Rotation is separate from the move vector).

 
Flag Post

Umm… yeah it didn’t work. When I test this out it actually starts shaking no matter what. All the math is in radians, the turnSpeed is 10 because the unit is meant to turn pretty quickly. So I don’t think theres a problem there.

 
Flag Post

However, taking facing out of the equation seems to have worked for some reason.

 
Flag Post

Well I mean doing this


Rotation += facing * Math.min(angleDiff, elapsedTime *
turnSpeed);

 
Flag Post

Using a dot product and a sign-switching variable is silly. Use the cross product instead, it preserves the sign of the angle:

var angleDiff:Number = Math.asin(
                    Vector2.Cross(wantedForward, forward));

… but since it’s 2D, the cross product is simply (x1y2)×(x2y1) so

var angleDiff:Number = Math.asin(wantedForward.x*forward.y - forward.x*wantedForward.y);
 
Flag Post

That didn’t seem to work, here’s what I did:


var angleDiff:Number = Math.asin(Vector2.Cross(wantedForward,
forward));

if (angleDiff > 0.001) { Rotation += Math.min(angleDiff, elapsedTime * turnSpeed); }

Instead of turning properly it just starts doing some crazy shit.

 
Flag Post
Originally posted by Arloistale:

However, taking facing out of the equation seems to have worked for some reason.

The reason being it had no business being in it in the first place, as I explained. But hey, you’re welcome.

Edit: BobJanova, using cross-products and asin only works if the angle between the two vectors is in the [-90;90] degree range. In order to get the full angle you still need the dot product (its sign really) to determine the actual angle modulo 360 degrees.

Edit 2: Hahaha I didn’t see that:

Originally posted by Arloistale:

All the math is in radians, the turnSpeed is 10 because the unit is meant to turn pretty quickly

10 radians per unit of time? How much is elapsedTime, and in what units? If you’re counting elapsedTime in ms and run at 30fps elapsedTime would be about 33 on average so you’d be turning at 330 radians per frame, or 52 turns and change. ‘pretty quickly’ indeed. If elapsedTime is in frames and you’re updating every frame then you’re ‘only’ doing one-and-a-half turn every frame, but it’s still nonsense. Only if you’ve set elapsedTime to be in seconds would you get a reasonable turn speed.

What I’m wondering is how you can have written two full classes and still be so uncritical of your own code and so passive when people suggest things to help you. You give the impression of trying out the things we suggest, make no effort to build up on them or further investigate the problems, and just come back to this thread for more suggestions. Have some initiative!

 
Flag Post
Originally posted by Arloistale:

That didn’t seem to work, here’s what I did:


var angleDiff:Number = Math.asin(Vector2.Cross(wantedForward,
forward));

if (angleDiff > 0.001)
{
Rotation += Math.min(angleDiff, elapsedTime *
turnSpeed);
}


Instead of turning properly it just starts doing some crazy shit.

Sorry about the double-post but this is just mind-boggling.

You do realize that BobJanova suggested using cross products so your angleDiff variable would be signed by default, right? By testing against 0.001 (hello hard-coded magic number!) you’re directly discarding every case where angleDiff is negative. So, FYI, ‘it’ is not doing ‘some crazy shit’. You are.

 
Flag Post

Aaand triple post! How deep does this rabbit hole go? Nobody knows. In the meantime this:

		public static function AngleBetween(vector1:Vector2, vector2:Vector2):Number
		{
			return Math.atan2(vector2.y - vector1.y, vector2.x - vector1.x);
		}

is garbage.

Let’s take an example. Vector 1 is (5,5), vector 2 is (3,3). They’re obviously colinear, so the angle between them should be 0 (or pi, but in this case zero). By your formula (hint: the formula is wrong) the angle is actually Math.atan2(5-3, 5-3) = pi / 4. So yea, there’s that, too.

Edit: See, this is what I mean when I say you’re not critical of your own code. When I type up a function, I immediately test it by tracing the output it provides for every single special case, as well as for a general case for which I know what the answer should be. Had you done that, you would never have left this monstrosity standing.

 
Flag Post
Edit: BobJanova, using cross-products and asin only works if the angle between the two vectors is in the [-90;90] degree range. In order to get the full angle you still need the dot product (its sign really) to determine the actual angle modulo 360 degrees.

A fair point, although at least it degrades gracefully in that it turns you in the right direction for any target except one directly behind you. Most tracking algorithms assume that you’re pointing in roughly the right direction already so you can usually ignore this. But you’re absolutely right to point it out.

And regarding the triple post (ccccombo breaker :D): I have a unit testing framework, and that really helps. I post-hoc added tests for my collision detection/resolution code recently and I’ve found three bugs so far … and I’m a professional programmer for the day job, I’m probably making less dumb mistakes than most people.

 
Flag Post

Yes, the elapsedTime is measured in seconds as I assumed you would be smart enough to understand. If it had been turning like some kind of epileptic demon then of course I would not have let that stand. Furthermore, if these functions were “monstrosities” then I would be back here, as you say by being “not critical of my own code,” asking why the functions like AngleBetween didn’t work. It does work because I use Vector2 in place of Point so imagine the vectors you mentioned as points on a graph. The angle from the first (3,3) to second (5,5) is quite clearly 45 degrees. Unless you transformed the axis so that the x axis is along the line formed between (3,3) to (5,5) then I don’t see how it could be 0 or PI. The AngleBetween is meant to find the angle between the vectors relative to a normal system. However, I realize I did not take the Cross product solution seriously, and I’m sorry for that, but you seem to have already pointed out the flaw in using that method.

 
Flag Post

Originally posted by Arloistale:

Yes, the elapsedTime is measured in seconds as I assumed you would be smart enough to understand.

why would we know it’s in seconds? it could be milliseconds or nanoseconds, days, hours, minutes or even tens of billions of years

also, your algorithm is absurdly slow: it looks like you’re intentionally choosing the slowest way possible to write code, then screwing over anyone with an older computer by using time-based physics instead of frame-based (equal to assuming the time between frames is always 33ms). do you work for zynga? they make things absurdly slow too

 
Flag Post
Originally posted by BobJanova:

at least it degrades gracefully in that it turns you in the right direction for any target except one directly behind you.

True that. One thing neither of us pointed out that still bears saying, is that using the cross product also removes the need to keep track of the right-hand-side direction that the OP needed (the ‘right’ variable.) And that’s icing on the sign-tracking cake.

So, OP, does it work finally, or is it still bugged?

Edit: “asking why the functions like AngleBetween didn’t work. It does work because I use Vector2 in place of Point so imagine the vectors you mentioned as points on a graph.”

That’s not what your own comments say:

		 * Calculates the angle, in radians, between two vectors.
		 * @param	vector1 The first vector.
		 * @param	vector2 The second vector.
		 * @return The angle between the two vectors.

I’m not going to go off on a rant about the pointlessness of comments but, if I was, I’d use these as textbook examples. Still, if you’ve written them, at least stand by them.

 
Flag Post

Skyboy, if this method is absurdly slow, then what is a faster method? Cos / sin calculations? I used time based movement for better accuracy, since I’ve found frames to be very inaccurate. I don’t understand why you are giving me criticism without telling me why it should be criticized…

 
Flag Post

Ace, I’ve renamed the function AngleBetween to DifferenceAngle if that makes the nonexistant future users of this class happy :)

 
Flag Post

To keep it simple:

If you use frames and the game lags, everything lags the same, including the frame-based timer and all calculations based on that.

If you use timers like that, everything gets screwed because of the difference between elapsed time and actual “game time”.

http://www.fastswf.com/YLYkQqI
Click on it and press space to start causing lag. Give it a while and look at the Frame/Timer difference to see how lag can impact timer based logic.

 
Flag Post

But isn’t using elapsedTime supposed to solve this kind of problem where timing is inaccurate because of lag? What is the best way to stay consistent then? Using frame based in the past always made timing inaccurate

 
Flag Post

Originally posted by Arloistale:

But isn’t using elapsedTime supposed to solve this kind of problem where timing is inaccurate because of lag? What is the best way to stay consistent then? Using frame based in the past always made timing inaccurate

inaccurate against real time, yes; but if your game requires active input (it’s not turn-based) then lag will cause things to move faster with less reaction time: input only occurs once per frame.

Originally posted by Arloistale:

Skyboy, if this method is absurdly slow, then what is a faster method? Cos / sin calculations? I used time based movement for better accuracy, since I’ve found frames to be very inaccurate. I don’t understand why you are giving me criticism without telling me why it should be criticized…

instead of even using your vector class at all, inline everything and use a regular point: Math.<x> is slow because it’s a static call; hiding that behind another static call doubles your overhead. creating new objects all other place you don’t use consumes substantial time, hiding a fast operation (like subtraction) behind a function call and a static look up is one of the worst slowdowns: well over 30x slower. Point itself isn’t too fast either because x/y are getters. there are a dozen other things in the code you’ve posted

 
Flag Post

Frames aren’t perfect, but in this case, a frame based logic would be way more reliable.

You can throw a getTimer() in your main logic loop and do something to compensate for any lag, but to be honest, a standard ENTER_FRAME listener will do just fine.

Yes, frames aren’t the best to keep rack of actual time, but that’s irrelevant when the alternative is to totally screw gameplay for people with slow machines.
As an example, I remember playing a game where levels were timed. I have an old desktop computer and the timer bar used to be half depleted by the time the intro animation was over, because the game used a timer instead of frames. No need to say it was totally unplayable for me and many others.

 
Flag Post

So the best method is to just do something like this?

                velocity.x = wantedForward.x * moveSpeed;
				velocity.y = wantedForward.y * moveSpeed;

Just do all the math there instead?