As chux already answered your question and you accepted his answer, here is an alternative way to look at it both from the math & programming perspectives. This can be applied to any three distinctive relative points.
Math - This will be done in 2D space, but can be applied to any dimensional space.
- Points will be donated by lower case where vectors will be uppercase.
- The Dot Product will be denoted by
*
- A length of a vector is the same as its magnitude.
- Points will be shown with their axis components (x,y)
- Vectors will be shown with their vector components (i,j)
- Theta will be the angle between two vectors & Phi will be the outside angle
> **Abstraction** - Rules & Equations of Vectors
> We have three points `a(x,y)`, `b(x,y)` & `c(x,y)`
> From These we can construct two vectors `A` & `B`
> `A = ca` and `B = cb`
> `A = `
> `B = `
> Now that we have defined two vectors `A` & `B` lets find `Theta`.
> In order to find the `cos(angle)` between them we need to know both
> the magnitudes of `A` & `B` and the `Dot Product` between them.
Magnitude
or Length
of a vector is sqrt( (i^2) + (j^2) )
Dot Product
is A*B = (Ai x Bi) + (Aj x Bj)
Cos(Angle) = (A*B) / (magA * magB)
Theta = acos( Cos(Angle) )
Phi = 360 - Theta
Proof - An example with points being a(5,7)
, b(3,5)
& c(5,3)
A = < 5-5, 7-3 > = < 0, 4 >
B = < 3-5, 5-3 > = < -2, 2 >
magA = sqrt( 0^2 + 4^2 ) = 4
magB = sqrt( (-2)^2 + 2^2 ) = 2sqrt(2)
cosAngle = A*B / (magA * magB)
cosAngle = (0*-2 + 4*2) = 8 / ( 4 x 2sqrt(2) ) = 0.7071067812
Theta = acos( cosAngle ) = 45 degrees
Phi = 360 - 45 = 315
Phi
is the clockwise angle
that you were asking for in the first diagram on the left
where Theta
is the angle between any two vectors.
The only thing that is left is to now apply these equations to your programming language of choice.
Note - This will only find the Angle Theta between two vectors and subtracting Theta from 360 to find Phi will give you the exterior angle around those two vectors. This does not incorporate or imply any direction of rotation of the angles themselves. This does not distinguish between clockwise or counterclockwise. The user would have to calculate that for themselves. This is just using basic properties and operations on 3 points or 2 vectors to find the angle between them which is only a single step of the full problem. If you refer to the proof above where the interior angle is 45 degrees and the exterior angle is 315; if you change point b to be (7,5) instead where point b is reflected over vector A then the output will be the exact same values: 45 degrees for the angle between the two vectors and 315 for the exterior angle. This does not know which direction you are rotating in; unless if you considered using and carrying about the sign of the cosine function if it is - or + then it might make a difference, but remembering from Trig cosine is + and - in different quadrants as well.
Programming - C++
main.cpp
#include <iostream>
#include "Vector2.h"
int main() {
// We can assume that points and vectors are similar except that points
// Don't have a direction where vectors do.
Vector2 pointA( 5, 7 );
Vector2 pointB( 3, 5 );
Vector2 pointC( 5, 3 );
Vector2 vec1 = pointA - pointC;
Vector2 vec2 = pointB - pointC;
// This is the actual angle
float ThetaA = vec1.getAngle( vec2, false, false );
// Other Option
float ThetaB = vec1.getCosAngle( vec2, false );
ThetaB = acos( ThetaB );
ThetaB = Math::radian2Degree( ThetaB );
// Same as other option above which this is already being done in getAngle()
float ThetaC = Math::radian2Degree( acos( vec1.getCosAngle( vec2, false ) ) );
std::cout << "ThetaA = " << ThetaA << std::endl;
std::cout << "ThetaB = " << ThetaB << std::endl;
std::cout << "ThetaC = " << ThetaC << std::endl;
float Phi = 360.0f - ThetaA;
std::cout << "Phi = " << Phi << std::endl;
return 0;
}
Output
ThetaA = 45
ThetaB = 45
ThetaC = 45
Phi = 315
Short Version of Main
#include <iostream>
#include "Vector2.h"
int main() {
Vector2 pointA( 5, 7 );
Vector2 pointB( 3, 5 );
Vector2 pointC( 5, 3 );
Vector2 vec1 = pointA - pointC;
Vector2 vec2 = pointB - pointC;
float angle = vec1.getAngle( vec2, false, false );
float clockwiseAngle = 360 - angle;
std::cout << "Theta " << angle << std::endl;
std::cout << "Phi " << clockwiwseAngle << std::endl;
return 0;
}
Output
Theta = 45
Phi = 315
This is how you can find the clockwise angle from the 3 points in the fist diagram on the left. As for the diagram on the right just find the angle without subtracting it from 360. The used classes are below.
Vector2.h
#ifndef VECTOR2_H
#define VECTOR2_H
#include "GeneralMath.h"
class Vector2 {
public:
union {
float m_f2[2];
struct {
float m_fX;
float m_fY;
};
};
// Constructors
inline Vector2();
inline Vector2( float x, float y );
inline Vector2( float *pfv );
// Destructor
~Vector2(){}
// Operators
inline Vector2 operator+( const Vector2 &v2 ) const;
inline Vector2 operator+() const;
inline Vector2& operator+=( const Vector2 &v2 );
inline Vector2 operator-( const Vector2 &v2 ) const;
inline Vector2 operator-() const;
inline Vector2& operator-=( const Vector2 &v2 );
inline Vector2 operator*( const float &fValue ) const;
inline Vector2& operator*=( const float &fValue );
inline Vector2 operator/( const float &fValue ) const;
inline Vector2& operator/=( const float &fValue );
// Functions
inline void normalize();
inline void zero();
inline bool isZero() const;
inline float dot( const Vector2 v2 ) const;
inline float length2() const;
inline float length() const;
inline float getCosAngle( const Vector2 &v2, const bool bNormalized = false );
inline float getAngle( const Vector2 &v2, const bool bNormalized = false, bool bRadians = true );
// Pre Multiple Vector By A Scalar
inline friend Vector2 Vector2::operator*( const float &fValue, const Vector2 v2 ) {
return Vector2( fValue*v2.m_fX, fValue*v2.m_fY );
} // operator*
// Pre Divide Vector By A Scalar
inline friend Vector2 Vector2::operator/( const float &fValue, const Vector2 v2 ) {
Vector2 vec2;
if ( Math::isZero( v2.m_fX ) ) {
vec2.m_fX = 0.0f;
} else {
vec2.m_fX = fValue / v2.m_fX;
}
if ( Math::isZero( v2.m_fY ) ) {
vec2.m_fY = 0.0f;
} else {
vec2.m_fY = fValue / v2.m_fY;
}
return vec2;
} // operator/
}; // Vector2
inline Vector2::Vector2() : m_fX( 0.0f ), m_fY( 0.0f ) {
} // Vector2
inline Vector2::Vector2( float x, float y ) : m_fX( x ), m_fY( y ) {
} // Vector2
inline Vector2::Vector2( float *pfv ) {
m_fX = pfv[0];
m_fY = pfv[1];
} // Vector2
// Unary + Operator
inline Vector2 Vector2::operator+() const {
return *this;
} // operator+
// Binary + Take This Vector And Add Another Vector To It
inline Vector2 Vector2::operator+( const Vector2 &v2 ) const {
return Vector2( m_fX + v2.m_fX, m_fY + v2.m_fY );
} // operator+
// Add Two Vectors Together
inline Vector2 &Vector2::operator+=( const Vector2 &v2 ) {
m_fX += v2.m_fX;
m_fY += v2.m_fY;
return *this;
} // operator+=
// Unary - Operator: Negate Each Value
inline Vector2 Vector2::operator-() const {
return Vector2( -m_fX, -m_fY );
} // operator-
// Unary - Take This Vector And Subtract Another Vector From It
inline Vector2 Vector2::operator-( const Vector2 &v2 ) const {
return Vector2( m_fX - v2.m_fX, m_fY - v2.m_fY );
} // operator-
// Subtract Two Vectors From Each Other
inline Vector2 &Vector2::operator-=( const Vector2 &v2 ) {
m_fX -= v2.m_fX;
m_fY -= v2.m_fY;
return *this;
} // operator-=
// Post Multiply Vector By A Scalar
inline Vector2 Vector2::operator*( const float &fValue ) const {
return Vector2( m_fX * fValue, m_fY * fValue );
} // operator*
// Multiply This Vector By A Scalar
inline Vector2& Vector2::operator*=( const float &fValue ) {
m_fX *= fValue;
m_fY *= fValue;
return *this;
} // operator*
// Post Divide Vector By A Scalar
inline Vector2 Vector2::operator/( const float &fValue ) const {
Vector2 vec2;
if ( Math::isZero( fValue ) ) {
vec2.m_fX = 0.0f;
vec2.m_fY = 0.0f;
} else {
float fValue_Inv = 1/fValue;
vec2.m_fX = vec2.m_fX * fValue_Inv;
vec2.m_fY = vec2.m_fY * fValue_Inv;
}
return vec2;
} // operator/
// Divide This Vector By A Scalar Value
inline Vector2& Vector2::operator/=( const float &fValue ) {
if ( Math::isZero( fValue ) ) {
m_fX = 0.0f;
m_fY = 0.0f;
} else {
float fValue_Inv = 1/fValue;
m_fX *= fValue_Inv;
m_fY *= fValue_Inv;
}
return *this;
} // operator/=
// Make The Length Of This Vector Equal To One
inline void Vector2::normalize() {
float fMag;
fMag = sqrt( m_fX * m_fX + m_fY * m_fY );
if ( fMag <= Math::ZERO ) {
m_fX = 0.0f;
m_fY = 0.0f;
return;
}
fMag = 1/fMag;
m_fX *= fMag;
m_fY *= fMag;
} // normalize
// Return True if Vector Is ( 0, 0 )
inline bool Vector2::isZero() const {
if ( Math::isZero( m_fX ) && Math::isZero( m_fY ) ) {
return true;
} else {
return false;
}
} // isZero
// Set Vector To ( 0, 0 )
inline void Vector2::zero() {
m_fX = 0.0f;
m_fY = 0.0f;
} // zero
// Return The Length Of This Vector
inline float Vector2::length() const {
return sqrtf( m_fX * m_fX + m_fY * m_fY );
} // length
// Return The Length Of This Vector
inline float Vector2::length2() const {
return ( m_fX * m_fX + m_fY * m_fY );
} // length2
// Return The Dot Product Between THIS Vector And Another Vector
inline float Vector2::dot( const Vector2 v2 ) const {
return ( m_fX * v2.m_fX + m_fY * v2.m_fY );
} // dot
// Returns The cos(Angle) Value Between THIS Vector And Vector v2.
// This Is Less Expensive Than Using GetAngle()
inline float Vector2::getCosAngle( const Vector2 &v2, const bool bNormalized ) {
// A . B = |A||B|cos(angle)
// -> cos-1((A.B)/(|A||B|))
float fMagA = length();
if ( fMagA <= Math::ZERO ) {
// This (A) Is An Invalid Vector
return 0;
}
float fValue = 0.0f;
if ( bNormalized ) {
// v2 Already Normalized
fValue = dot(v2) / fMagA;
} else {
// v2 Not Normalized
float fMagB = v2.length();
if ( fMagB <= Math::ZERO ) {
// B Is An Invalid Vector
return 0;
}
fValue = dot(v2) / ( fMagA * fMagB );
}
// Correct Value Due To Rounding Problems
Math::constrain( -1.0f, 1.0f, fValue );
return fValue;
} // getCosAngle
// Returns The Angle Between THIS Vector And Vector v2 In RADIANS
inline float Vector2::getAngle( const Vector2 &v2, const bool bNormalized, bool bRadians ) {
// A . B = |A||B|cos(angle)
// -> cos-1((A.B)/(|A||B|))
if ( bRadians ) {
return acos( getCosAngle( v2, bNormalized ) );
} else {
// Convert To Degrees
return Math::radian2Degree( acos( getCosAngle( v2, bNormalized ) ) );
}
} // GetAngle
#endif // VECTOR2_H
GeneralMath.h
#ifndef GENERALMATH_H
#define GENERALMATH_H
#include <math.h>
class Math {
public:
static const float PI;
static const float PI_HALVES;
static const float PI_THIRDS;
static const float PI_FOURTHS;
static const float PI_SIXTHS;
static const float PI_2;
static const float PI_INVx180;
static const float PI_DIV180;
static const float PI_INV;
static const float ZERO;
Math();
inline static bool isZero( float fValue );
inline static float sign( float fValue );
inline static int randomRange( int iMin, int iMax );
inline static float randomRange( float fMin, float fMax );
inline static float degree2Radian( float fDegrees );
inline static float radian2Degree( float fRadians );
inline static float correctAngle( float fAngle, bool bDegrees, float fAngleStart = 0.0f );
inline static float mapValue( float fMinY, float fMaxY, float fMinX, float fMaxX, float fValueX );
template<class T>
inline static void constrain( T min, T max, T &value );
template<class T>
inline static void swap( T &value1, T &value2 );
}; // Math
// Convert Angle In Degrees To Radians
inline float Math::degree2Radian( float fDegrees ) {
return fDegrees * PI_DIV180;
} // degree2Radian
// Convert Angle In Radians To Degrees
inline float Math::radian2Degree( float fRadians ) {
return fRadians * PI_INVx180;
} // radian2Degree
// Returns An Angle Value That Is Alway Between fAngleStart And fAngleStart + 360
// If Radians Are Used, Then Range Is fAngleStart To fAngleStart + 2PI
inline float Math::correctAngle( float fAngle, bool bDegrees, float fAngleStart ) {
if ( bDegrees ) {
// Using Degrees
if ( fAngle < fAngleStart ) {
while ( fAngle < fAngleStart ) {
fAngle += 360.0f;
}
} else if ( fAngle >= (fAngleStart + 360.0f) ) {
while ( fAngle >= (fAngleStart + 360.0f) ) {
fAngle -= 360.0f;
}
}
return fAngle;
} else {
// Using Radians
if ( fAngle < fAngleStart ) {
while ( fAngle < fAngleStart ) {
fAngle += Math::PI_2;
}
} else if ( fAngle >= (fAngleStart + Math::PI_2) ) {
while ( fAngle >= (fAngleStart + Math::PI_2) ) {
fAngle -= Math::PI_2;
}
}
return fAngle;
}
} // correctAngle
// Tests If Input Value Is Close To Zero
inline bool Math::isZero( float fValue ) {
if ( (fValue > -ZERO) && (fValue < ZERO) ) {
return true;
}
return false;
} // isZero
// Returns 1 If Value Is Positive, -1 If Value Is Negative Or 0 Otherwise
inline float Math::Sign( float fValue ) {
if ( fValue > 0 ) {
return 1.0f;
} else if ( fValue < 0 ) {
return -1.0f;
}
return 0;
} // sign
// Return A Random Number Between iMin And iMax Where iMin < iMax
inline int Math::randomRange( int iMin, int iMax ) {
if ( iMax < iMin ) {
swap( iMax, iMin );
}
return (iMin + ((iMax - iMin +1) * rand()) / (RAND_MAX+1) );
} // randomRange
// Return A Random Number Between fMin And fMax Where fMin < fMax
inline float Math::randomRange( float fMin, float fMax ) {
if ( fMax < fMin ) {
swap( fMax, fMin );
}
return (fMin + (rand()/(float)RAND_MAX)*(fMax-fMin));
} // randomRange
// Returns The fValueY That Corresponds To A Point On The Line Going From Min To Max
inline float Math::mapValue( float fMinY, float fMaxY, float fMinX, float fMaxX, float fValueX ) {
if ( fValueX >= fMaxX ) {
return fMaxY;
} else if ( fValueX <= fMinX ) {
return fMinY;
} else {
float fM = (fMaxY - fMinY) / (fMaxX - fMinX);
float fB = fMaxY - fM * fMaxX;
return (fM*fValueX + fB);
}
} // mapValue
// Constrain a Value To Be Between T min & T max
template<class T>
inline void Math::constrain( T min, T max, T &value ) {
if ( value < min ) {
value = min;
return;
}
if ( value > max ) {
value = max;
}
} // constrain
// Swap Two Values
template<class T>
inline void Math::Swap( T &value1, T &value2 ) {
T temp;
temp = value1;
value1 = value2;
value2 = temp;
} // swap
#endif // GENERALMATH_H
GeneralMath.cpp
#include "GeneralMath.h"
const float Math::PI = 4.0f * atan(1.0f); // tan(pi/4) = 1 or acos(-1)
const float Math::PI_HALVES = 0.50f * Math::PI;
const float Math::PI_THIRDS = Math::PI * 0.3333333333333f;
const float Math::PI_FOURTHS = 0.25f * Math::PI;
const float Math::PI_SIXTHS = Math::PI * 0.6666666666667f;
const float Math::PI_2 = 2.00f * Math::PI;
const float Math::PI_DIV180 = Math::PI / 180.0f;
const float Math::PI_INVx180 = 180.0f / Math::PI;
const float Math::PI_INV = 1.0f / Math::PI;
const float Math::ZERO = (float)1e-7;
Math::Math() {
} // Math