Calculate the General Direction of a Vector

Hi All,

Given a point of origin and a Vector3 extending therefrom, I’m trying to work how to calculate the ‘general direction’ of that Vector. By ‘general direction’ I mean whether the Vector is mostly point forwards, backwards, left, right, up or down.

It’s probably easier to explain what I’m after using the 2D example below :

20665-example.png

As displayed, the point of origin (x) is in the centre of the circle. The circle is split into four 90 degree quadrants. The position of each quadrant (relative to a notional direction of which way is forwards) determines whether it represents forward, back, left or right.
In the above, the green arrow represents a Vector2 that is facing forwards. The yellow arrow a Vector facing to the left.

I can calculate the ‘general direction’ in the 2D examples above using a bit of trig, but what I’m really struggling with is the math to apply this in 3D.
Consider the circle below is actually a sphere that, as well as outlining forward, back, left and right, also has Up and Down based on the vertical (or y) aspect of the Vector.

I could probably hash something together by checking the Vector3’s x, y and z angle (relative to the origin’s x,y and z planes) individually and then place the vector in the relevant quadrant with piles of switch statements or nested if’s but that approach has so many variables to account for and just doesn’t feel right. I’m hoping there’s a math boffin out there that could provide a smarter and slicker approach?

Many thanks in advance for any suggestions!

I don’t consider myself a "math boffin’, but the Dot product might help here. If you take the Dot products between the direction vectors and the unknown vector, the highest value will indicate which vector it is closest to in direction. Here is a bit of untested code:

#pragma strict

var compass = [Vector3.left, Vector3.right, Vector3.forward, Vector3.back, Vector3.up, Vector3.down];

function ClosestDirection(v : Vector3) : Vector3{

	var maxDot = -Mathf.Infinity;
	var ret = Vector3.zero;
	
	for( var dir : Vector3 in compass) { 
		var t = Vector3.Dot(v, dir);
		if (t > maxDot) {
			ret = dir;
			maxDot = t;
		}
	}

	return ret;
}

Note this code returns the vector. You could change to an indexed ‘for’ loop and return the index or even an enumeration for the direction.

robertbu’s solution is precise, it will tell you exactly which axis the vector is closest to pointing along. I thought I’d mention an alternative though which is simpler to implement and gives good intuitive results that are not quite so precise but usually good enough.

This is for a normalized vector - if yours is not normalized, then normalize a copy of it first.

if (vector.z > thresholdZ)
    // pointing forwards
elif (vector.z < -thresholdZ)
    // pointing backwards
elif (vector.y > thresholdY)
    // pointing up
elif (vector.y < -thresholdY)
    // pointing down
elif (vector.x > 0)
    // pointing right
else
    // pointing left

For the first threshold, use a value like 0.6-0.7 - the lower the value, the more keen the algorithm is to say you’re pointing forwards or backwards. For the second threshold you might lean towards higher numbers like 0.7-0.8, to make the algorithm not so keen to say you’re looking up or down (i.e. more likely to say you’re looking left or right).

(Mathematically, thresholdZ is the cosine of the largest angle offset from straight forwards which you still consider to be looking forwards, and thresholdY is similar for up and down.)

Thanks Chaps, both very helpful suggestions.

Robertbu - I still haven’t quite got my around exactly how the dot function works - I just need to meditate on it for a while longer, but I do see how the results can translate into what I’m after - returning a custom enum will be the way forward for me.

Gfoot - I appreciate the alternative. My current project requires the precision of robertbu’s solution and is based on equal weightings between each direction but it’s a good call on being able to adjust the thresholds to apply bias’.

For what it’s worth, I came up with another alternative myself. It’s no more effective that robertbu’s solution but I thought I’d mention it for the benefit of any future readers.

The idea is that you define a point infront, behind, above, below, to the left and to the right of the point of origin directly along each axis. It does matter how far they are from the point of origin, as long as they are all the same distance.
Then, you just check the distance between point at the end of the vector you’re testing and each of the six points previously defined.
The general direction can be defined from which ever of those six points is closest to the point at the end of you vector.

Something like this …

public enum GeneralDirection {
	None,
	Forwards,
	Back,
	Left,
	Right,
	Up,
	Down
}

	public GeneralDirection GetDirection (Vector3 pointOfOrigin, Vector3 vectorToTest) {
		
		GeneralDirection result = GeneralDirection.None;
		float shortestDistance = Mathf.Infinity;
		float distance = 0;
		
		Vector3 vectorPosition = pointOfOrigin + vectorToTest;

		distance = Mathf.Abs (((pointOfOrigin + Vector3.forward) - vectorToTest).magnitude);
		if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Forwards;
		}
		distance = Mathf.Abs (((pointOfOrigin  -Vector3.forward) - vectorToTest).magnitude);
			if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Back;
		}
		distance = Mathf.Abs (((pointOfOrigin + Vector3.up) - vectorToTest).magnitude);
			if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Up;
		}
		distance = Mathf.Abs (((pointOfOrigin + -Vector3.up) - vectorToTest).magnitude);
			if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Down;
		}
		distance = Mathf.Abs (((pointOfOrigin + Vector3.left) - vectorToTest).magnitude);
			if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Left;
		}
		distance = Mathf.Abs (((pointOfOrigin + Vector3.right) - vectorToTest).magnitude);
			if (distance < shortestDistance)
		{
			shortestDistance = distance;
			result = GeneralDirection.Right;
		}

		return result;
		
	}
	
	
	
	

}

It’s not quite as elegant as robertbu’s solution but I reckon it gives the same degree of accuracy but uses ‘Simpler Maths’ - not that that’s a tangible benefit I suppose :wink:

It does, however, combine what I think are the strengths of both robertbu’s and gfoot’s solutions in that it should be just as precise as robertbu’s but also you could easily apply a bias to any particular direction by defining the point in front/behind/etc closer or further away from the point of origin - e.g. defining the point above the point of origin closer would apply bias toward it and moving it further away would do the opposite.
This solution would also work whether you normalize the vector or not.

Anyway, as I said, just a thought that may be a better for someone else’s needs.

Thanks again Gents.