I am trying to find the intersect between a straight line and a quadratic curve, however the result I am getting appears to be imaginary although I don't see how this can be the case as I can see them intersect on real axes:
Import numpy
#quadratic coefficients
a,b,c = (-3.09363812e-04, 1.52138019e+03, -1.87044961e+09)
# y = ax^2 + bx + c
#line coefficients
m,d = (1.06446434e-03, -2.61660911e+03)
#y = mx + d
intersect = (-(b-m)+((b-m)**2 - 4*a*(c-d))**0.5)/(2*a)
print(intersect)
The output of this is 2458883.4674943495-107.95731226786134j
I am trying to find the intersect between the yellow curve over the blue points and the black dotted line
The graphed curves you presented vs. your equations are not the same, and your equations do not intersect.
I rewrote some example code for you. numpy isn't needed, and an exact solution is possible.
import math
import collections
def calculateIntersection(p, l):
b = p.B - l.slope
c = p.C - l.yInt
discriminant = b**2 - (4 * p.A * c)
if discriminant > 0.0:
# 2 points of intersection
x1 = (-b + math.sqrt(discriminant)) / (2.0 * p.A)
x2 = (-b - math.sqrt(discriminant)) / (2.0 * p.A)
return discriminant, [(x1, l.slope * x1 + l.yInt), (x2, l.slope * x2 + l.yInt)]
elif discriminant == 0.0:
# 1 point of intersection
x1 = -b / (2.0 * p.A)
return discriminant, [(x1, slope * x1 + l.yInt)]
else:
# no points of intersection
return discriminant, []
Line = collections.namedtuple('Line', 'slope yInt')
Poly = collections.namedtuple('Poly', 'A B C')
p = Poly(A=-3.09363812e-04, B=1.52138019e+03, C=-1.87044961e+09)
print(p)
l = Line(slope=1.06446434e-03, yInt=-2.61660911e+03)
print(l)
(discriminant, points) = calculateIntersection(p, l)
if (len(points) > 0):
print("Intersection: {}".format(points))
else:
print("No intersection: {}".format(discriminant))
Related
I am working on a python script and I am sure there is an easier way to approach this problem then my solution so far.
Give two coordinates, say (0,0) and (40,40) and a set distance to travel, say 5, how would I find a new coordinate pair for the point that is 5 units from (0,0) heading along the line that connects (0,0) and (40,40)?
I am doing the following given the distance_to_travel and points p0, p1:
ang = math.atan2(p1.y - p0.y, p1.x - p0.x)
xx = node0.x + (distance_to_travel * math.cos(ang))
yy = node0.y + (distance_to_travel * math.sin(ang))
Following the method outlined in my comment:
# find the vector a-b
ab.x, ab.y = p1.x - p0.x, p1.y - p0.y
# find the length of this vector
len_ab = (ab.x * ab.x + ab.y * ab.y) ** 0.5
# scale to length 5
ab5.x, ab5.y = ab.x *5 / len_ab, ab.y *5 / len_ab
# add to a (== p0)
fin.x, fin.y = p0.x + ab5.x, p0.y + ab5.y
You should find the slope of the line between a and b |ab|.
Then you can use 2D spherical (polar) coordinate system to get r mount of distance with a given slope (The slope you found earlier).
Now you can convert from spherical to Cartesian to find x, y coordinates of new point. Then you add this values to values of point a:
import math
def slope(p1, p2):
return math.atan2(
(p2[1] - p1[1]), (p2[0] - p1[0])
)
def spherical_to_cartesian(r, theta):
x = r * math.cos(theta)
y = r * math.sin(theta)
return x, y
def add_points(p1, p2):
return p1[0] + p2[0], p1[1] + p2[1]
if __name__ == '__main__':
a = [0, 0]
b = [40, 40]
slp = slope(a, b)
r = 5
new_p = spherical_to_cartesian(r, slp)
res = add_points(a, new_p)
print(res)
I use Mayavi to plot implicit 3d surfaces and I would like to combine those surfaces into one plot. However, when I do this I get something similar like this:
The code I am using:
import numpy as np
from mayavi import mlab
A=0.24639243776
B=5.39100472027e-17
C=1.71555149594
D=1.72967325617
E=7.50535440036
F=-1.17072847143
G=1.0
x, y, z = np.ogrid[-3:1:100j, -10:1:100j, 0:3:100j]
def Fun (x,y,z, A,B,C,D,E,F,G):
F1 = (x - A + y - B) / 2 + np.sqrt(((x - A - y + B) / 2) ** 2 + C * z ** 2)
F2 = np.sqrt(x*((F*y)/2 + (E*x)/D) + y*((F*x)/2 + (D*y)/E) + G*z**2) - np.sqrt(D*E)
F1 [F1 < 0] = F2 [F1 < 0]
return F1
Fu = Fun (x,y,z,A,B,C,D,E,F,G)
mlab.contour3d(Fu, contours = [0])
mlab.show()
The problem is that no matter how I do it one of the surfaces is very low poly! I had plotted the same in Mathematica and it produces a perfect plot:
I do not expect to get the same quality plot as Mathematica is a commercial product. But at least I would like for both surfaces to be smooth.
If Mayavi is not the right tool for the task, maybe you could recommend something different with which I could achieve the desired result.
Thanks in advance!
I figured it out. The key is to set the surface to None for it to be trimmed. Then the other surface can be drawn in the same fashion. Furthermore for the surface to be plotted according to scale, x,y,z has to be also passed into the contour3d
import numpy as np
from mayavi import mlab
A=0.24639243776
B=5.39100472027e-17
C=1.71555149594
D=1.72967325617
E=7.50535440036
F=-1.17072847143
G=1.0
x, y, z = np.mgrid[-3:1:300j, -10:1:300j, 0:3:300j]
def Fun1 (x,y,z, A,B,C,D,E,F,G):
F1 = (x - A + y - B) / 2 + np.sqrt(((x - A - y + B) / 2) ** 2 + C * z ** 2)
F2 = np.sqrt(x*((F*y)/2 + (E*x)/D) + y*((F*x)/2 + (D*y)/E) + G*z**2) - np.sqrt(D*E)
F1 [F2 > 0.0] = None
return F1
def Fun2 (x,y,z, A,B,C,D,E,F,G):
F1 = (x - A + y - B) / 2 + np.sqrt(((x - A - y + B) / 2) ** 2 + C * z ** 2)
F2 = np.sqrt(x*((F*y)/2 + (E*x)/D) + y*((F*x)/2 + (D*y)/E) + G*z**2) - np.sqrt(D*E)
F2 [F1 > 0.0] = None
return F2
Fu1 = Fun1 (x,y,z,A,B,C,D,E,F,G)
Fu2 = Fun2 (x,y,z,A,B,C,D,E,F,G)
mlab.contour3d(x,y,z,Fu1, contours = [0])
mlab.contour3d(x,y,z,Fu2, contours = [0])
mlab.show()
And here is the result:
I've searched far and wide but have yet to find a suitable answer to this problem. Given two lines on a sphere, each defined by their start and end points, determine whether or not and where they intersect. I've found this site (http://mathforum.org/library/drmath/view/62205.html) which runs through a good algorithm for the intersections of two great circles, although I'm stuck on determining whether the given point lies along the finite section of the great circles.
I've found several sites which claim they've implemented this, Including some questions here and on stackexchange, but they always seem to reduce back to the intersections of two great circles.
The python class I'm writing is as follows and seems to almost work:
class Geodesic(Boundary):
def _SecondaryInitialization(self):
self.theta_1 = self.point1.theta
self.theta_2 = self.point2.theta
self.phi_1 = self.point1.phi
self.phi_2 = self.point2.phi
sines = math.sin(self.phi_1) * math.sin(self.phi_2)
cosines = math.cos(self.phi_1) * math.cos(self.phi_2)
self.d = math.acos(sines - cosines * math.cos(self.theta_2 - self.theta_1))
self.x_1 = math.cos(self.theta_1) * math.cos(self.phi_1)
self.x_2 = math.cos(self.theta_2) * math.cos(self.phi_2)
self.y_1 = math.sin(self.theta_1) * math.cos(self.phi_1)
self.y_2 = math.sin(self.theta_2) * math.cos(self.phi_2)
self.z_1 = math.sin(self.phi_1)
self.z_2 = math.sin(self.phi_2)
self.theta_wraps = (self.theta_2 - self.theta_1 > PI)
self.phi_wraps = ((self.phi_1 < self.GetParametrizedCoords(0.01).phi and
self.phi_2 < self.GetParametrizedCoords(0.99).phi) or (
self.phi_1 > self.GetParametrizedCoords(0.01).phi) and
self.phi_2 > self.GetParametrizedCoords(0.99))
def Intersects(self, boundary):
A = self.y_1 * self.z_2 - self.z_1 * self.y_2
B = self.z_1 * self.x_2 - self.x_1 * self.z_2
C = self.x_1 * self.y_2 - self.y_1 * self.x_2
D = boundary.y_1 * boundary.z_2 - boundary.z_1 * boundary.y_2
E = boundary.z_1 * boundary.x_2 - boundary.x_1 * boundary.z_2
F = boundary.x_1 * boundary.y_2 - boundary.y_1 * boundary.x_2
try:
z = 1 / math.sqrt(((B * F - C * E) ** 2 / (A * E - B * D) ** 2)
+ ((A * F - C * D) ** 2 / (B * D - A * E) ** 2) + 1)
except ZeroDivisionError:
return self._DealWithZeroZ(A, B, C, D, E, F, boundary)
x = ((B * F - C * E) / (A * E - B * D)) * z
y = ((A * F - C * D) / (B * D - A * E)) * z
theta = math.atan2(y, x)
phi = math.atan2(z, math.sqrt(x ** 2 + y ** 2))
if self._Contains(theta, phi):
return point.SPoint(theta, phi)
theta = (theta + 2* PI) % (2 * PI) - PI
phi = -phi
if self._Contains(theta, phi):
return spoint.SPoint(theta, phi)
return None
def _Contains(self, theta, phi):
contains_theta = False
contains_phi = False
if self.theta_wraps:
contains_theta = theta > self.theta_2 or theta < self.theta_1
else:
contains_theta = theta > self.theta_1 and theta < self.theta_2
phi_wrap_param = self._PhiWrapParam()
if phi_wrap_param <= 1.0 and phi_wrap_param >= 0.0:
extreme_phi = self.GetParametrizedCoords(phi_wrap_param).phi
if extreme_phi < self.phi_1:
contains_phi = (phi < max(self.phi_1, self.phi_2) and
phi > extreme_phi)
else:
contains_phi = (phi > min(self.phi_1, self.phi_2) and
phi < extreme_phi)
else:
contains_phi = (phi > min(self.phi_1, self.phi_2) and
phi < max(self.phi_1, self.phi_2))
return contains_phi and contains_theta
def _PhiWrapParam(self):
a = math.sin(self.d)
b = math.cos(self.d)
c = math.sin(self.phi_2) / math.sin(self.phi_1)
param = math.atan2(c - b, a) / self.d
return param
def _DealWithZeroZ(self, A, B, C, D, E, F, boundary):
if (A - D) is 0:
y = 0
x = 1
elif (E - B) is 0:
y = 1
x = 0
else:
y = 1 / math.sqrt(((E - B) / (A - D)) ** 2 + 1)
x = ((E - B) / (A - D)) * y
theta = (math.atan2(y, x) + PI) % (2 * PI) - PI
return point.SPoint(theta, 0)
def GetParametrizedCoords(self, param_value):
A = math.sin((1 - param_value) * self.d) / math.sin(self.d)
B = math.sin(param_value * self.d) / math.sin(self.d)
x = A * math.cos(self.phi_1) * math.cos(self.theta_1) + (
B * math.cos(self.phi_2) * math.cos(self.theta_2))
y = A * math.cos(self.phi_1) * math.sin(self.theta_1) + (
B * math.cos(self.phi_2) * math.sin(self.theta_2))
z = A * math.sin(self.phi_1) + B * math.sin(self.phi_2)
new_phi = math.atan2(z, math.sqrt(x**2 + y**2))
new_theta = math.atan2(y, x)
return point.SPoint(new_theta, new_phi)
EDIT: I forgot to specify that if two curves are determined to intersect, I then need to have the point of intersection.
A simpler approach is to express the problem in terms of geometric primitive operations like the dot product, the cross product, and the triple product. The sign of the determinant of u, v, and w tells you which side of the plane spanned by v and w contains u. This enables us to detect when two points are on opposite sites of a plane. That's equivalent to testing whether a great circle segment crosses another great circle. Performing this test twice tells us whether two great circle segments cross each other.
The implementation requires no trigonometric functions, no division, no comparisons with pi, and no special behavior around the poles!
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def dot(v1, v2):
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
def cross(v1, v2):
return Vector(v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x)
def det(v1, v2, v3):
return dot(v1, cross(v2, v3))
class Pair:
def __init__(self, v1, v2):
self.v1 = v1
self.v2 = v2
# Returns True if the great circle segment determined by s
# straddles the great circle determined by l
def straddles(s, l):
return det(s.v1, l.v1, l.v2) * det(s.v2, l.v1, l.v2) < 0
# Returns True if the great circle segments determined by a and b
# cross each other
def intersects(a, b):
return straddles(a, b) and straddles(b, a)
# Test. Note that we don't need to normalize the vectors.
print(intersects(Pair(Vector(1, 0, 1), Vector(-1, 0, 1)),
Pair(Vector(0, 1, 1), Vector(0, -1, 1))))
If you want to initialize unit vectors in terms of angles theta and phi, you can do that, but I recommend immediately converting to Cartesian (x, y, z) coordinates to perform all subsequent calculations.
Intersection using plane trig can be calculated using the below code in UBasic.
5 'interx.ub adapted from code at
6 'https://rosettacode.org
7 '/wiki/Find_the_intersection_of_two_linesSinclair_ZX81_BASIC
8 'In U Basic by yuji kida https://en.wikipedia.org/wiki/UBASIC
10 XA=48.7815144526:'669595.708
20 YA=-117.2847245001:'2495736.332
30 XB=48.7815093807:'669533.412
40 YB=-117.2901673467:'2494425.458
50 XC=48.7824947147:'669595.708
60 YC=-117.28751374:'2495736.332
70 XD=48.77996737:'669331.214
80 YD=-117.2922957:'2494260.804
90 print "THE TWO LINES ARE:"
100 print "YAB=";YA-XA*((YB-YA)/(XB-XA));"+X*";((YB-YA)/(XB-XA))
110 print "YCD=";YC-XC*((YD-YC)/(XD-XC));"+X*";((YD-YC)/(XD-XC))
120 X=((YC-XC*((YD-YC)/(XD-XC)))-(YA-XA*((YB-YA)/(XB-XA))))/(((YB-YA)/(XB-XA))-((YD-YC)/(XD-XC)))
130 print "Lat = ";X
140 Y=YA-XA*((YB-YA)/(XB-XA))+X*((YB-YA)/(XB-XA))
150 print "Lon = ";Y
160 'print "YCD=";YC-XC*((YD-YC)/(XD-XC))+X*((YD-YC)/(XD-XC))
Hey trying to learn how to code and I cant figure this exercise out.
Specifically getting the precise y axis intercept points.
The formula given works for getting the x axis points but I cant figure out how to get the y axis points.
Exercise :
Input : Radius of circle and the y - intercept of the line.
Output : Circle drawn with a horizontal line across the window with the given y intercept. Mark two points of the intersection.
Print the x values of the points of intersection *Formula : x = ± √r^2 - y^2
Code::
from graphics import *
from math import *
def main():
# enter radius and the y intercept of the line
radius = eval(input("Put in radius:: "))
yinter = eval(input("Put in y intersec:: "))
#Draw window + circle + line
win = GraphWin()
win.setCoords(-10.0, -10.0, 10.0, 10.0)
circle = Circle(Point(0.0,0.0), radius)
mcircle = Circle(Point(0.0,0.0), 0.5)
circle.draw(win)
mcircle.draw(win)
line = Line(Point(-10, 0), Point(10, yinter))
line.draw(win)
#Calculate x axis points of intersept
xroot1 = sqrt(radius * radius - yinter * yinter)
xroot2 = -abs(xroot1)
print("Xroot 1 : ", xroot1)
print("Xroot 2 : ", xroot2)
x = 0
yroot1 = sqrt(radius * radius - x * x)
yroot2 = -abs(yroot1)
print("Yroot 1 : ", yroot1)
print("Yroot 2 : ", yroot2)
#mark two points of intersept in red
sc1 = Circle(Point(xroot1, yroot1), 0.3)
sc1.setFill('red')
sc2 = Circle(Point(xroot2, yroot2), 0.3)
sc2.setFill('red')
sc1.draw(win)
sc2.draw(win)
main()
Answer - With Radius of 8 and Y intersect point of 2
Yroot1 = 7.75
Yroot2 = -7.75
Xroot1 = 8.0
Xroot2 = -8.0
I just came up with a subroutine to find intersection points while solving another Zelle-graphics related SO question. There may be ways to simplify the math but I'm going the long way around:
from graphics import *
def intersection(center, radius, p1, p2):
""" find the two points where a secant intersects a circle """
dx, dy = p2.x - p1.x, p2.y - p1.y
a = dx**2 + dy**2
b = 2 * (dx * (p1.x - center.x) + dy * (p1.y - center.y))
c = (p1.x - center.x)**2 + (p1.y - center.y)**2 - radius**2
discriminant = b**2 - 4 * a * c
assert (discriminant > 0), 'Not a secant!'
t1 = (-b + discriminant**0.5) / (2 * a)
t2 = (-b - discriminant**0.5) / (2 * a)
return Point(dx * t1 + p1.x, dy * t1 + p1.y), Point(dx * t2 + p1.x, dy * t2 + p1.y)
def main(win):
center = Point(0.0, 0.0)
# Enter radius
radius = float(input("Put in radius: "))
# Draw circle and center dot
Circle(center, radius).draw(win)
Circle(center, 0.3).draw(win)
# Enter the y intercept of the line
yinter = float(input("Put in y intercept: "))
# Draw line
p1, p2 = Point(-10.0, 0.0), Point(10.0, yinter)
Line(p1, p2).draw(win)
# Mark two points of intercept in red
for i, root in enumerate(intersection(center, radius, p1, p2), start=1):
print("Root {}:".format(i), root)
dot = Circle(root, 0.3)
dot.setFill('red')
dot.draw(win)
win = GraphWin()
win.setCoords(-10.0, -10.0, 10.0, 10.0)
main(win)
win.getMouse()
OUTPUT
NOTE
You can get values input that do not produce a secant, e.g. a radius of 2 and an intercept of 8. Your code doesn't account for this -- the above will simply throw an assertion error if it occurs. But you can upgrade that to an error you can catch and fix.
For the y coordinates you can use a similar formula:
y = ± sqrt(r^2 - x^2)
and the do everything the same with marking the roots.
You should write the code like this:
x = sqrt(r ** 2 - y ** 2)
line = Line(Point(-10, 0), Point(10, yinter))
line.draw(win)
Line(Point(-10,0) is wrong, it should read:
Line(Point(-10,yinter).
Also set Xroot1 and Xroot2 = 0 or -x=0, x=0
def intersection(center, radius, p1, p2):
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
a = dx**2 + dy**2
b = 2 * (dx * (p1[0]- center[0]) + dy * (p1[1] - center[1]))
c = (p1[0] - center[0])**2 + (p1[1] - center[1])**2 - radius**2
K = b**2 - 4 * a * c
return True if K>0 else False
if __name__ == "__main__":
p1 = 10,50
p2 = 100,50
center= 50,150
radius = 600
print(intersection(center, radius, p1, p2))
Given 3 points in space (3D): A = (x1, y1, z1), B = (x2, y2, z2) C = (x3, y3, z3); then how to find the center and radius of the circle (arc) that passes through these three points, i.e. find circle equation? Using Python and Numpy here is my initial code
import numpy as np
A = np.array([x1, y1, z1])
B = np.array([x2, y2, z2])
C = np.array([x3, y3, z3])
#Find vectors connecting the three points and the length of each vector
AB = B - A
BC = C - B
AC = C - A
# Triangle Lengths
a = np.linalg.norm(AB)
b = np.linalg.norm(BC)
c = np.linalg.norm(AC)
From the Circumradius definition, the radius can be found using:
R = (a * b * c) / np.sqrt(2.0 * a**2 * b**2 +
2.0 * b**2 * c**2 +
2.0 * c**2 * a**2 -
a**4 - b**4 - c**4)
However, I am having problems finding the Cartesian coordinates of the center. One possible solution is to use the "Barycentric Coordinates" of the triangle points to find the trilinear coordinates of the circumcenter (Circumcenter).
First (using this source) we find the circumcenter barcyntric coordinates:
#barcyntric coordinates of center
b1 = a**2 * (b**2 + c**2 - a**2)
b2 = b**2 * (c**2 + a**2 - b**2)
b3 = c**2 * (a**2 + b**2 - c**2)
Then the Cartesian coordinates of the center (P) would be:
Px = (b1 * A[0]) + (b2 * B[0]) + (b3 * C[0])
Py = (b1 * A[1]) + (b2 * B[1]) + (b3 * C[1])
Pz = (b1 * A[2]) + (b2 * B[2]) + (b3 * C[2])
However, the barcyntric coordinates values above do not seem to be correct. When solved with an example of known values, the radius is correct, but the coordinates of the center are not.
Example: For these three points:
A = np.array([2.0, 1.5, 0.0])
B = np.array([6.0, 4.5, 0.0])
C = np.array([11.75, 6.25, 0.0])
The radius and center coordinates are:
R = 15.899002930062595
P = [13.4207317073, -9.56097560967, 0]
Any ideas on how to find the center coordinates?
There are two issues with your code.
The first is in the naming convention. For all the formulas you are using to hold, the side of length a has to be the one opposite the point A, and similarly for b and B and c and C. You can solve that by computing them as:
a = np.linalg.norm(C - B)
b = np.linalg.norm(C - A)
c = np.linalg.norm(B - A)
The second has to do with the note in your source for the barycentric coordinates of the circumcenter: not necessarily homogeneous. That is, they need not be normalized in any way, and the formula you are using to compute the Cartesian coordinates from the barycentric ones is only valid when they add up to one.
Fortunately, you only need to divide the resulting Cartesian coordinates by b1 + b2 + b3 to get the result you are after. Streamlining a little bit your code for efficiency, I get the results you expect:
>>> A = np.array([2.0, 1.5, 0.0])
>>> B = np.array([6.0, 4.5, 0.0])
>>> C = np.array([11.75, 6.25, 0.0])
>>> a = np.linalg.norm(C - B)
>>> b = np.linalg.norm(C - A)
>>> c = np.linalg.norm(B - A)
>>> s = (a + b + c) / 2
>>> R = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c))
>>> b1 = a*a * (b*b + c*c - a*a)
>>> b2 = b*b * (a*a + c*c - b*b)
>>> b3 = c*c * (a*a + b*b - c*c)
>>> P = np.column_stack((A, B, C)).dot(np.hstack((b1, b2, b3)))
>>> P /= b1 + b2 + b3
>>> R
15.899002930062531
>>> P
array([ 13.42073171, -9.56097561, 0. ])
As an extension to the original problem: Now suppose we have an arc of known length (e.g. 1 unit) extending from point A as defined above (away from point B). How to find the 3D coordinates of the end point (N)? The new point N lies on the same circle passing through points A, B & C.
This can be solved by first finding the angle between the two vectors PA & PN:
# L = Arc Length
theta = L / R
Now all we need to do is rotate the vector PA (Radius) by this angle theta in the correct direction. In order to do that, we need the 3D rotation matrix. For that we use the Euler–Rodrigues formula:
def rotation_matrix_3d(axis, theta):
axis = axis / np.linalg.norm(axis)
a = np.cos(theta / 2.0)
b, c, d = axis * np.sin(theta / 2.0)
rot = np.array([[a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c)],
[ 2*(b*c+a*d), a*a+c*c-b*b-d*d, 2*(c*d-a*b)],
[ 2*(b*d-a*c), 2*(c*d+a*b), a*a+d*d-b*b-c*c]])
return rot
Next, we need to find the rotation axis to rotate PA around. This can be achieved by finding the axis normal to the plane of the circle passing through A, B & C. The coordinates of point N can then be found from the center of the circle.
PA = P - A
PB = P - B
xx = np.cross(PB, PA)
r3d = rotation_matrix_3d(xx, theta)
PN = np.dot(r3d, PA)
N = P - PN
as an example, we want to find coordinates of point N that are 3 degrees away from point A, the coordinates would be
N = (1.43676498, 0.8871264, 0.)
Which is correct! (manually verified using CAD program)