4

I have two points A(X,Y) and B(P,Q) in the first quadrant. There's another point C(L,M). How do I find angle between CA and CB in clockwise direction?

I searched a lot and all the solution used atan2() but it finds the angle from origin with respect to x axis.

C and A can be assumed fixed. And B is can be anywhere in the first quadrant

C and A can be assumed fixed. And B is can be anywhere in the first quadrant. The angle must be clockwise and within range 0-360 (Or 0 to 360-1).

I am doing this in C/C++.

Edit : Adding code per request. This is a bit different, because I got stuck at a concept and needed clarification regarding it. This function ought to return if the point x,y lies between 50,50 and P. P is the angle with respect to CA.

bool isInsideAngle(long double x,long double y, long double p)
{   
    if((atan2(y,x) >= atan2(50,100)) && (atan2(y,x) <= (p * PI / 50)))
    {
        // cout<<"YES!";
        //   cout<<" atan2(y,x) = " <<atan2(y,x)*180/PI<<endl;
        //   cout<<" atan2(50,50) = " <<atan2(50,100)*180/PI<<endl;
        //   cout<<" (p * PI / 50) = "<<(p * PI / 50)*180/PI<<endl;
        return true;
    }

    else
        return false;

 }
Jongware
  • 22,200
  • 8
  • 54
  • 100
Saurabh Shrivastava
  • 1,055
  • 1
  • 12
  • 26

3 Answers3

5

How do I find angle between CA and CB in clockwise direction?

// The atan2 functions return arctan y/x in the interval [−π , +π] radians
double Dir_C_to_A = atan2(Ay - Cy, Ax - Cx);
double Dir_C_to_B = atan2(By - Cy, Bx - Cx);
double Angle_ACB = Dir_C_to_A - Dir_C_to_B;

// Handle wrap around
const double Pi = acos(-1);  // or use some π constant
if (Angle_ACB > Pi) Angle_ACB -= 2*Pi;
else if (Angle_ACB < -Pi) Angle_ACB += 2*Pi;

// Answer is in the range of [-pi...pi]
return Angle_ACB;
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Thank you! This is exactly what I needed. Now I just need to convert angles to angles in the range [0,360]. (The above code also yields negative angles). – Saurabh Shrivastava Jan 07 '17 at 05:35
  • @SaurabhShrivastava Better to scale by 180/pi then add 360 for negative values than to add and scale. The first method is numerically more stable. – chux - Reinstate Monica Jan 07 '17 at 05:37
  • Exactly what I am gonna do. – Saurabh Shrivastava Jan 07 '17 at 05:39
  • 1
    @SaurabhShrivastava Note: Your [0..360] solution will not need `if (Angle_ACB > Pi) Angle_ACB -= 2*Pi;` and `if (Angle_ACB < -Pi) Angle_ACB += 2*Pi;` --> `if (Angle_ACB < 0) Angle_ACB += 2*Pi;` – chux - Reinstate Monica Jan 07 '17 at 05:40
  • I am sorry? Would you please explain? – Saurabh Shrivastava Jan 07 '17 at 05:44
  • I simply removed the wrap around part and added ` if(Angle_ACB * 180/PI<0) return Angle_ACB * 180/PI + 360; return Angle_ACB * 180/PI;` Is it okay? – Saurabh Shrivastava Jan 07 '17 at 06:01
  • I don't see a good reason to use the range [-π,π]. This is a signed value which can be seen as counterclockwise when negative, which creates confusion. The range [0,2π) or (0,2π] makes more sense. –  Jan 07 '17 at 10:13
  • @YvesDaoust A good reason to use `[-π,π]` is precision. About 1/4 of all `double` are in the range `[-1,0]`. By adding `2π`, most `double` precision is lost for most values in that range. (OTOH, the prior difference may have lost precision.) Note that `[0,2π)` or `(0,2π]` is a mathematical concern. With `double`, `π` is not representable, the best is `machine_pi`. Result ranges like `[-machine_pi, machine_pi]` or `[0,machine_2pi]`, with inclusive endpoints, should be expected. – chux - Reinstate Monica Jan 07 '17 at 22:00
  • The precision argument is a weak one: it could just save 1 ulp, and the extra bit that you get after subtraction of 2π is meaningless (by the way, as the angle computation comes from coordinate differences, several of the low order bits are non-significant). –  Jan 08 '17 at 11:11
  • Reporting the angle in a closed interval like [-π,π] is a bad idea as it gives a false feeling that you can tell the difference between a zero-angle and a full-turn angle, which is not the case. –  Jan 08 '17 at 11:18
  • @YvesDaoust The precision issue is not weak. " several of the low order bits are non-significant" is not true depending on the location in the plane. Reporting the angle difference as just under 360.0 (or 2 pi) vs near -0.0 can lose many bits, if not all precision. Reporting in radians a full-turn angle as `π` is not possible as `π` is irrational and all finite `double` are rational. So the issue becomes one related to `machine_pi` or `machine_2pi`. The trouble is that the best machine values may be just greater than `π` or `2π`. This is a programming issue vs only a math one. – chux - Reinstate Monica Jan 08 '17 at 16:47
  • @chux: The value minus two pi isn't any better than the original. Having more bits, but which are wrong is of no use. This is a matter of numerical analysis. –  Jan 08 '17 at 21:18
2

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
syloc
  • 4,569
  • 5
  • 34
  • 49
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Rather than `sqrtf( m_fX * m_fX + m_fY * m_fY );`, does your compiler allow `hypotf( m_fX, m_fY );`? "The `hypot` functions compute the square root of the sum of the squares of x and y, without undue overflow or underflow" – chux - Reinstate Monica Jan 07 '17 at 22:11
  • Do you get the same result with `Vector2` as `pointB( 3, 5 )` or `pointB( 7, 1 )` (180 degrees on the other side of `C`)? – chux - Reinstate Monica Jan 07 '17 at 22:34
  • @chux for your first question I'm not sure about `hypotf(...)` and for your second question this is a basic Vector2 class that I use in my older Game Engines before I started to use GLM's library. It has all the necessary functions that can be applied to vectors. The `getAngle()` method will always return the angle that is between 2 vectors. The last parameter `true / false` is for returning either radians or degrees. It has nothing to do with which way the angle is being rotated or oriented. But by knowing where one point is from another you can then figure out if you need to add or sub 360. – Francis Cugler Jan 08 '17 at 06:36
  • @chux As for the point's locations I choose those values specifically because I knew that they created a 45 degree angle. I can test out the second question for you... – Francis Cugler Jan 08 '17 at 06:37
  • @chux When I changed b to (7,1) the angle between them was 135 degrees where the exterior angle is 225. If I change b to (7,5) I get the same output as the original. Remember this is only a method to find the actual angle between two vectors. You will have to calculate and take into consideration what direction of the rotation of the angle is. I should edit my answer to make a note of that to the reader. – Francis Cugler Jan 08 '17 at 06:45
1

This solution uses vectors to solve your problem. It relies on the fact that, given two vectors u and v, the cosine of the smallest angle between them is (uv / |u||v|), where uv is the dot product of u and v. To find whether the sense of the smallest angle from u to v is positive or negative, i.e., counter-clockwise or clockwise, the triple-product is used. The triple product of three vectors u, v, and w is (wuXv), and can be interpreted as the signed volume of the parallelepiped defined by the three vectors. Since the cross-product (and hence the triple-product) is only defined for vectors in R^3, the vectors used here are 3-vectors, with the points of interest lying in the XY-plane. The third vector forming the parallelepiped lies in the positive Z-direction, so that a positive result for the triple-product indicates that the smallest angle between u and v has a counter-clockwise sense.

The function smallest_angle() returns the smallest angle between two vectors in radians. The clockwise_angle() function returns the clockwise angle in radians from the first vector u to the second vector v. The function angle_about_c() returns the clockwise angle in radians from the line segment CA to CB. Note that the points A, B, and C are taken to be vectors.

#include <stdio.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

struct Vector {
    double i;
    double j;
    double k;
};

double magnitude(struct Vector u);
struct Vector vect_diff(struct Vector u, struct Vector v);
double dot_product(struct Vector u, struct Vector v);
struct Vector cross_product(struct Vector u, struct Vector v);
double triple_product(struct Vector u, struct Vector v, struct Vector w);
double smallest_angle(struct Vector u, struct Vector v);
double clockwise_angle(struct Vector u, struct Vector v);
double angle_about_c(struct Vector a, struct Vector b, struct Vector c);

int main(void)
{
    struct Vector vect_a = { .i = 1, .j = 1 };
    struct Vector vect_b = { .i = 0, .j = 1 };

    printf("Smallest angle:  %f rad\n", smallest_angle(vect_a, vect_b));
    printf("Clockwise angle: %f rad\n", clockwise_angle(vect_a, vect_b));

    struct Vector point_a  = { .i = 3, .j = 3 };
    struct Vector point_b1 = { .i = 4, .j = 2 };
    struct Vector point_b2 = { .i = 2, .j = 2 };
    struct Vector point_c  = { .i = 3, .j = 1 };

    printf("Clockwise angle from CA to CB1: %f rad\n",
           angle_about_c(point_a, point_b1, point_c));
    printf("Clockwise angle from CA to CB2: %f rad\n",
           angle_about_c(point_a, point_b2, point_c));

    return 0;
}

double magnitude(struct Vector u)
{
    return sqrt(dot_product(u, u));
}

struct Vector vect_diff(struct Vector u, struct Vector v)
{
    return (struct Vector) { .i = u.i - v.i,
                             .j = u.j - v.j,
                             .k = u.k - v.k };
}

double dot_product(struct Vector u, struct Vector v)
{
    return (u.i * v.i) + (u.j * v.j) + (u.k * v.k);
}

struct Vector cross_product(struct Vector u, struct Vector v)
{
    return (struct Vector) { .i = (u.j * v.k) - (u.k * v.j),
                             .j = (u.k * v.i) - (u.i * v.k),
                             .k = (u.i * v.j) - (u.j * v.i) };
}

double triple_product(struct Vector u, struct Vector v, struct Vector w)
{
    return dot_product(u, cross_product(v, w));
}

double smallest_angle(struct Vector u, struct Vector v)
{
    return acos(dot_product(u, v) / (magnitude(u) * magnitude(v)));
}

double clockwise_angle(struct Vector u, struct Vector v)
{
    double angle_s = smallest_angle(u, v);

    if (triple_product((struct Vector) { 0, 0, 1 }, u, v) > 0) {
        angle_s = 2 * M_PI - angle_s;
    }

    return angle_s;
}

double angle_about_c(struct Vector a, struct Vector b, struct Vector c)
{
    return clockwise_angle(vect_diff(a, c), vect_diff(b, c));
}

Program output:

Smallest angle:  0.785398 rad
Clockwise angle: 5.497787 rad
Clockwise angle from CA to CB1: 0.785398 rad
Clockwise angle from CA to CB2: 5.497787 rad
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • 1
    Perhaps the downvoter will have the courtesy to explain what is wrong with my answer? – ad absurdum Jan 08 '17 at 21:42
  • Looks good to me. It does report a potential full circle range. – chux - Reinstate Monica Jan 09 '17 at 00:21
  • @chux-- Thanks. I saw your comments about reported range under your answer; will think about that some more. It looks like our drive-by-downvoter hit your answer as well! My answer is rather verbose; my aim wasn't efficiency but rather to model a certain geometric viewpoint. Maybe this was the objection? – ad absurdum Jan 09 '17 at 00:32
  • 1
    Votes (up or down) with a comment are generally more informative, efficient and useful than votes (up or down) without a comment. Those silent DVs are [water off a duck](http://idioms.thefreedictionary.com/like+water+off+a+duck's+back). – chux - Reinstate Monica Jan 09 '17 at 00:42