As the comments indicated, if the only purpose for arcus cosinus is to get the angle between two vectors, another approach using the two-parameter atan
is preferable.
I ran some experiments in C and Kahan's formula 2 * atan2 (norm (x * norm (y) - norm (x) * y), norm (x * norm (y) + norm (x) * y)) works well, as does the more commonly used atan2 (norm (cross (x, y)), dot (x, y)). Best I can tell, C's atan2 is equivalent to Postscript's two-argument atan function. –
njuffa -Oct 23 at 23:21
So I tried to implement it (for 3D vectors (x y z)):
First the norm
(length), named n3
:
% norm 3D vector
%(x y z)
/n3 { dup mul exch dup mul add exch dup mul add sqrt } def
Then three helpers to multiply (v3m
), add (v3a
), and subtract (v3s
) 3D vectors (for those not fearing stack rotations):
% multiply 3D vector with factor
%(x y z f)
/v3m {
dup 3 1 roll mul 4 1 roll %(z*f x y f)
dup 3 1 roll mul 3 1 roll %(z*f y*f x f)
mul exch 3 -1 roll %(x*f y*f z*f)
} def
% subtract 3D vectors
%(x1 y1 z1 x2 y2 z2)
/v3s {
6 -3 roll %(x2 y2 z2 x1 y1 z1)
4 -1 roll sub %(x2 y2 x1 y1 z1-z2)
5 1 roll 3 -1 roll sub %(z1-z2 x2 x1 y1-y2)
4 1 roll exch sub %(y1-y2 z1-z2 x1-x2)
3 1 roll %(x1-x2 y1-y2 z1-z2)
} def
% add 3D vectors
%(x1 y1 z1 x2 y2 z2)
/v3a {
4 -1 roll add %(x1 y1 x2 y2 z2+z1)
5 1 roll 3 -1 roll add %(z2+z1 x1 x2 y2+y1)
4 1 roll add %(y2+y1 z2+z1 x1+x2)
3 1 roll %(x1+x2 y2+y1 z2+z1)
} def
Eventually the angle function (a
) is a bit different, because I wanted to avoid heavy stack shuffling, so converted the vectors to arrays and assigned the arrays to a name each.
I think it makes the code also a bit more readable, while a bit less efficient (you may try to use copy
to duplicate the vectors' coordinates without using arrays and names (and the additional local dictionary):
% angle between two 3D vectors
%(x1 y1 z1 x2 y2 z2)
/a {
[ 4 1 roll ]
4 1 roll
[ 4 1 roll ]
%([v2] [v1])
2 dict begin
/v1 exch def
/v2 exch def
v1 aload pop n3 v2 aload pop
4 -1 roll v3m
%(|v1|*v2)
v2 aload pop n3 v1 aload pop
4 -1 roll v3m
%(|v1|*v2 |v2|*v1)
v3s n3
%(||v1|*v2-|v2|*v1|)
v1 aload pop n3 v2 aload pop
4 -1 roll v3m
%(|v1|*v2)
v2 aload pop n3 v1 aload pop
4 -1 roll v3m
%(|v1|*v2 |v2|*v1)
v3a n3
%(||v1|*v2-|v2|*v1| ||v1|*v2+|v2|*v1|)
atan
%(atan(||v1|*v2-|v2|*v1|,||v1|*v2+|v2|*v1|))
2.0 mul
end
} def
Finally here are some test cases:
GS>0 0 1 0 0 1 a ==
0.0
GS>0 0 1 0 1 0 a ==
90.0
GS>0 0 1 0 1 1 a ==
45.0
GS>0 0 1 1 0 0 a ==
90.0
GS>0 0 1 1 0 1 a ==
45.0
GS>0 0 1 1 1 0 a ==
90.0
GS>0 0 1 1 1 1 a ==
54.7356071
%
GS>0 1 0 0 0 1 a ==
90.0
GS>0 1 0 0 1 0 a ==
0.0
GS>0 1 0 0 1 1 a ==
45.0
GS>0 1 0 1 0 0 a ==
90.0
GS>0 1 0 1 0 1 a ==
90.0
GS>0 1 0 1 1 0 a ==
45.0
GS>0 1 0 1 1 1 a ==
54.7356071
%
GS>0 1 1 0 0 1 a ==
45.0
GS>0 1 1 0 1 0 a ==
45.0
GS>0 1 1 0 1 1 a ==
0.0
GS>0 1 1 1 0 0 a ==
90.0
GS>0 1 1 1 0 1 a ==
59.9999962
GS>0 1 1 1 1 0 a ==
59.9999962
GS>0 1 1 1 1 1 a ==
35.264389
%
GS>1 0 0 0 0 1 a ==
90.0
GS>1 0 0 0 1 0 a ==
90.0
GS>1 0 0 0 1 1 a ==
90.0
GS>1 0 0 1 0 0 a ==
0.0
GS>1 0 0 1 0 1 a ==
45.0
GS>1 0 0 1 1 0 a ==
45.0
GS>1 0 0 1 1 1 a ==
54.7356071
%
GS>1 0 1 0 0 1 a ==
45.0
GS>1 0 1 1 1 0 a ==
59.9999962
GS>1 0 1 0 1 1 a ==
59.9999962
GS>1 0 1 1 0 0 a ==
45.0
GS>1 0 1 1 0 1 a ==
0.0
GS>1 0 1 1 1 0 a ==
59.9999962
GS>1 0 1 1 1 1 a ==
35.264389
%
GS>1 1 0 0 0 1 a ==
90.0
GS>1 1 0 0 1 0 a ==
45.0
GS>1 1 0 0 1 1 a ==
59.9999962
GS>1 1 0 1 0 0 a ==
45.0
GS>1 1 0 1 0 1 a ==
59.9999962
GS>1 1 0 1 1 0 a ==
0.0
GS>1 1 0 1 1 1 a ==
35.264389
%
GS>1 1 1 0 0 1 a ==
54.7356071
GS>1 1 1 0 1 0 a ==
54.7356071
GS>1 1 1 0 1 1 a ==
35.264389
GS>1 1 1 1 0 0 a ==
54.7356071
GS>1 1 1 1 0 1 a ==
35.264389
GS>1 1 1 1 1 0 a ==
35.264389
GS>1 1 1 1 1 1 a ==
0.0
I hope I didn't mess it up.