Search code examples
rvector3danglesigned

How to calculate signed angles between 3 points


I have a problem with calculating the sign of angles between 3 points. The head of my dataframe looks like this:

        x       y       z
6554 158.485 170.917 177.917
6228 159.138 171.466 179.099
6698 159.088 170.504 180.273
1833 157.945 169.852 180.449
1875 157.720 168.950 181.571
1893 156.800 167.829 181.119

I made a loop to calculate the angle between them by using the a.b = |a|*|b|*cos(alpha). Unfortunatelly with this, I can't get sign, because the acos function will give me answers only from [0,pi] interval. This is my loop:

  for(kh in 1:nrow(CA_list)) {
  CA_list[kh,4] <- CA_list[kh,1]-CA_list[kh+1,1]
  CA_list[kh,5] <- CA_list[kh,2]-CA_list[kh+1,2]
  CA_list[kh,6] <- CA_list[kh,3]-CA_list[kh+1,3]
}

colnames(CA_list)[4] <- "vecx"
colnames(CA_list)[5] <- "vecy"
colnames(CA_list)[6] <- "vecz"

CA_list$abs <- sqrt(CA_list$vecx^2+CA_list$vecy^2+CA_list$vecz^2)

for(kg in 1:nrow(CA_list)) {
  CA_list[kg,8] <- CA_list[kg,4]*CA_list[kg+1,4]+CA_list[kg,5]*CA_list[kg+1,5]+CA_list[kg,6]*CA_list[kg+1,6]
  CA_list[kg,9] <- CA_list[kg,7]*CA_list[kg+1,7]
}

colnames(CA_list)[8] <- "LHS"
colnames(CA_list)[9] <- "RHS"

CA_list$angles <- acos(CA_list$LHS/CA_list$RHS)

the dataframe will look like this:

           x       y       z   vecx   vecy   vecz      abs      LHS      RHS   angles
6554 158.485 170.917 177.917 -0.653 -0.549 -1.182 1.457715 0.826880 2.213722 68.06684
6228 159.138 171.466 179.099  0.050  0.962 -1.174 1.518624 0.890998 2.016130 63.77260
6698 159.088 170.504 180.273  1.143  0.652 -0.176 1.327603 1.042751 1.934437 57.38127
1833 157.945 169.852 180.449  0.225  0.902 -1.122 1.457091 0.710998 2.213313 71.26225
1875 157.720 168.950 181.571  0.920  1.121  0.452 1.518995 0.884453 2.016499 63.98489
1893 156.800 167.829 181.119 -0.401  1.233 -0.285 1.327522 1.067264 1.932921 56.48529

Now I want complete my loop with some function that can calculate the signs of the angles. What would be the easiest method for me?


Solution

  • The signed angle between two 2D-vectors u and v can be obtained with the help of the atan2 function:

    angle_unsigned <- function(u, v){
      acos( sum(u*v) / ( sqrt(sum(u*u)) * sqrt(sum(v*v)) ) )
    }
    
    angle_signed <- function(u, v){
      atan2(v[2], v[1]) - atan2(u[2], u[1]) 
    }
    

    EDIT

    In 3D, you need to have a direction, represented by a vector n. The angle3D_signed function below returns the angle in [0,2pi[ by which the vector u must rotate counterclockwise, as seen from the direction defined by n, to reach the vector v.

    crossProduct <- function(v, w){ 
      c(
        v[2] * w[3] - v[3] * w[2], 
        v[3] * w[1] - v[1] * w[3], 
        v[1] * w[2] - v[2] * w[1]
      )
    }
    
    angle3D_signed <- function(n, u, v){
      n <- n / sqrt(sum(n*n)) # normalize n
      unorm <- sqrt(sum(u*u))
      vnorm <- sqrt(sum(v*v))
      s <- sum(crossProduct(n, u) * v) # "unnormalized sine"
      cosAngle <- sum(u*v) / unorm / vnorm
      ifelse(s >= 0, acos(cosAngle), 2*pi - acos(cosAngle))
    }