20

I have 3 points containing X, Y, Z coordinates:

var A = {x: 100, y: 100, z: 80},
    B = {x: 100, y: 175, z: 80},
    C = {x: 100, y: 100, z: 120};

The coordinates are pixels from a 3d CSS transform. How can I get the angle between vectors BA and BC? A math formula will do, JavaScript code will be better. Thank you.

enter image description here

JumpingJezza
  • 5,498
  • 11
  • 67
  • 106
Mircea
  • 11,373
  • 26
  • 64
  • 95

4 Answers4

39

In pseudo-code, the vector BA (call it v1) is:

v1 = {A.x - B.x, A.y - B.y, A.z - B.z}

Similarly the vector BC (call it v2) is:

v2 = {C.x - B.x, C.y - B.y, C.z - B.z}

The dot product of v1 and v2 is a function of the cosine of the angle between them (it's scaled by the product of their magnitudes). So first normalize v1 and v2:

v1mag = sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z)
v1norm = {v1.x / v1mag, v1.y / v1mag, v1.z / v1mag}

v2mag = sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z)
v2norm = {v2.x / v2mag, v2.y / v2mag, v2.z / v2mag}

Then calculate the dot product:

res = v1norm.x * v2norm.x + v1norm.y * v2norm.y + v1norm.z * v2norm.z

And finally, recover the angle:

angle = acos(res)
Roger Rowland
  • 25,885
  • 11
  • 72
  • 113
  • Nice, just let me put it in Javascript – Mircea Nov 01 '13 at 15:44
  • would you have a jsfiddle of the result @Mircea ? – y_nk Apr 29 '14 at 12:27
  • 1
    @y_nk Sorry no fiddle but look trough http://typefolly.com/interaction/ as it should be in there – Mircea Apr 29 '14 at 15:44
  • man, isn't there something more effective and less precise? acos and two sqrts in a render loop can build up slow quite fast – user151496 Jan 16 '16 at 20:28
  • @user151496 well the question wasn't about speed and of course there are fast sqrt and trig algorithms but why would you ever need to calculate the actual angle inside a render loop? – Roger Rowland Jan 17 '16 at 05:03
  • well to me it seems like it's almost always. you manipulate the objects somehow, then want to perform some logic based on their location. i mean, the possibilities when you need to do trigonometry in the render loop are limitless. btw i'm not trying to hate your algorithm or anything, i was just trying to find something faster – user151496 Jan 17 '16 at 18:45
  • @user151496 Well, two points: 1) there are very fast algorithms in shaders that make many optimizations unnecessary, and 2) trigonometry in this context is just a different way of viewing linear algebra. The algebra itself is almost always faster and vectorizes well - knowledge of an actual angle is mostly a step on the way somewhere else - jump that step with the linear algebra and avoid any need for the computation in the first place. It's always quicker to do nothing ;-) – Roger Rowland Jan 17 '16 at 20:22
  • @user151496 Don't worry, I'm not offended - you can't really "hate" my algorithm, because it isn't mine anyway, it's just an expression of the fact that the dot product of two unit vectors is simply the cosine of the angle between them. The question was clear and the answer appropriate - if you need something faster in a different context, that's a different question with a different solution. Why not ask it? – Roger Rowland Jan 17 '16 at 20:26
  • Do we really need to normalize the vectors first? – Snehasish Karmakar Apr 15 '16 at 18:40
  • @SnehasishKarmakar the dot product is the cosine of the angle, scaled by the product of the magnitudes. So either normalise before or after. – Roger Rowland Apr 15 '16 at 19:11
  • 1
    @ Roger Rowland: Yeah, basically it is the same thing. It was stupid of me not to carefully notice before bothering you! :) – Snehasish Karmakar Apr 17 '16 at 13:07
  • @RogerRowland would it work to obtain torsion angles? – Another.Chemist Oct 09 '17 at 16:38
  • @Another.Chemist Well, I had to google for torsion angles and I guess you’re a chemist, so if I’ve understood correctly you’re talking about the angle between two planes, each of which is defined by three points? Although the technique handles three dimensions ok, it might be more convenient to use the alternative plane equation, where the plane is described as a combination of a point on the plane and a normal vector. Then the angle between the planes could be derived just from the two normals. – Roger Rowland Oct 09 '17 at 18:53
  • @Another.Chemist Note that this method would not recover the sign of the angle, which I see may be important in your application. – Roger Rowland Oct 09 '17 at 19:26
  • @RogerRowland thank you and yes, what you say is true. The application to chemistry can be exemplified through this image http://cbio.bmt.tue.nl/pumma/uploads/Theory/dihedral.png and what I have done is in this post https://stackoverflow.com/questions/46651542/bash-how-to-obtain-a-torsion-angle-with-four-points – Another.Chemist Oct 09 '17 at 19:28
  • @y_nk Here's a gist of it coded it up in Javascript: https://gist.github.com/andfaulkner/c4ad12a72d29bcd653eb4b8cca2ae476 – Andrew Faulkner Feb 13 '19 at 09:40
6
double GetAngleABC( double* a, double* b, double* c )
{
    double ab[3] = { b[0] - a[0], b[1] - a[1], b[2] - a[2] };
    double bc[3] = { c[0] - b[0], c[1] - b[1], c[2] - b[2]  };

    double abVec = sqrt(ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2]);
    double bcVec = sqrt(bc[0] * bc[0] + bc[1] * bc[1] + bc[2] * bc[2]);

    double abNorm[3] = {ab[0] / abVec, ab[1] / abVec, ab[2] / abVec};
    double bcNorm[3] = {bc[0] / bcVec, bc[1] / bcVec, bc[2] / bcVec};

    double res = abNorm[0] * bcNorm[0] + abNorm[1] * bcNorm[1] + abNorm[2] * bcNorm[2];

    return acos(res)*180.0/ 3.141592653589793;
}


double a[] = {1, 0, 0};

double b[] = {0, 0, 0};

double c[] = {0, 1, 0};

std::cout<< "The angle of ABC is " << GetAngleABC(a,b,c)<< "º " << std::endl;
Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123
Bruno Andrade
  • 271
  • 3
  • 5
  • This code is viable. I note a dangerous condition: you assume the length of the double* is always three without checking this. Where the user/coder passes an array of different length, this code will produce an exception, but you are not handling exceptions. It's a nitpicking thing, really, since the code works, however I prefer to use try/catch exception handling with ANY array operation (or similar operation, to include any collection with a length/size such as vector) AND I prefer to confirm that the length/size of the array/container is what the function expects (here being 3). Up-vote. – StephenDonaldHuffPhD Aug 18 '17 at 15:20
2

@Roger algorithm in swift

func SCNVector3Angle(start: SCNVector3, mid: SCNVector3, end: SCNVector3) -> Double {
    let v1 = (start - mid)
    let v2 = (end - mid)
    let v1norm = v1.normalized()
    let v2norm = v2.normalized()

    let res = v1norm.x * v2norm.x + v1norm.y * v2norm.y + v1norm.z * v2norm.z
    let angle: Double = Double(GLKMathRadiansToDegrees(acos(res)))
    return angle
}

/**
* Subtracts two SCNVector3 vectors and returns the result as a new SCNVector3.
*/
func - (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
    return SCNVector3Make(left.x - right.x, left.y - right.y, left.z - right.z)
}

extension SCNVector3
{
    /**
    * Returns the length (magnitude) of the vector described by the SCNVector3
    */
    func length() -> Float {
        return sqrtf(x*x + y*y + z*z)
    }

    /**
    * Normalizes the vector described by the SCNVector3 to length 1.0 and returns
    * the result as a new SCNVector3.
    */
    func normalized() -> SCNVector3 {
        return self / length()
    }
}
Chandan Shetty SP
  • 5,087
  • 6
  • 42
  • 63
2

The same in python (with output in degrees):

import numpy as np
import math 
import time

def angle_2p_3d(a, b, c):       

    v1 = np.array([ a[0] - b[0], a[1] - b[1], a[2] - b[2] ])
    v2 = np.array([ c[0] - b[0], c[1] - b[1], c[2] - b[2] ])

    v1mag = np.sqrt([ v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] ])
    v1norm = np.array([ v1[0] / v1mag, v1[1] / v1mag, v1[2] / v1mag ])

    v2mag = np.sqrt(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2])
    v2norm = np.array([ v2[0] / v2mag, v2[1] / v2mag, v2[2] / v2mag ])
    res = v1norm[0] * v2norm[0] + v1norm[1] * v2norm[1] + v1norm[2] * v2norm[2]
    angle_rad = np.arccos(res)

    return math.degrees(angle_rad)


p1 = np.array([1,0,0])
p2 = np.array([0,0,0])
p3 = np.array([0,0,1])

start = time.time()
angle= angle_2p_3d(p1, p2, p3)
end = time.time()

print("angle: ", angle)
print("elapsed in: ", end - start)

Output:

angle: 90.0

elapsed in: 8.392333984375e-05