Draw a Circle in 3D perpendicular to direction vector

179 Views Asked by At

You People might have seen similar question everywhere, I have checked almost all of them but none of them satisfies my requirement. :(

What I am Trying to achieve: Trying to draw a Tube and Bend using circular Rings and them joining them to form a tube and bend.

What Would be the best / Robust way to Draw a Circle in 3D ? I have following parameters:

  • Circle Center (global Coordinates)
  • Radius
  • Direction Vector V ( Point B (x,y,z) - Point A (x,y,z) )

Technique I already used:

  • Choose Arbitrary Vector v1 e.g. [1,0,0]
  • Cross Product to get a new othronormal vector v2 = CROSS ( V , v1 )
  • Draw Circle using v1 and v2

enter image description here here u = v1, and v= v2

Please Check the attached Code below:

def circularpoints_v3(center, radius, normal, num_points=12):
# normal == direction Vector (V)

    v1 =v2 = np.array([0. , 0., 0.]).astype(np.float64) #z
    if True:
        if abs(normal[0]) > abs(normal[2]):
            b2 = np.array([-normal[1], normal[0], 0.0])
        else:
            b2 = np.array([0.0, -normal[2], normal[1]])
        b2 /= np.linalg.norm(b2)
        b1 = np.cross(b2, normal)
        v1 = b2
        v2 = b1
    circle = []
    angles = np.linspace(0, 2 * np.pi, num=num_points)
    for angle in angles:
        v1_Out = np.cos(angle) * v1 
        v2_Out = np.sin(angle) * v2
        point = center + (radius * (v1_Out + v2_Out ))
        circle.append(point)

Issues: It is working perfectly, but I have almost every possible combination of Direction Vector V.

As a result, I need an orthonormal basis for every V. BUT sometimes, the basis form does not equal the previous basis. As a result, the Circle starting and ending point Differ. This is what I don't want because this would create a TWIST when I join all points.

enter image description here

3

There are 3 best solutions below

11
On

When you move from one tube segment to the next, you can apply an appropriate minimal 3D rotation to the circle basis in order to keep it pointing in the right direction without any twisting. If the previous segment direction vector is $U$ and the new one is $V$, then you want a rotation by angle $\cos^{-1}(U\cdot V)$ around axis $U\times V$. This also works for determining the initial rotation.

With the scipy Rotation module, it should be something like this:

circle = np.array([[cos(t), sin(t), 0] for t in np.linspace(...)])
basis = np.identity(3)
for segment in tube_path:
    dir = direction(segment)
    prev_dir = basis[2]
    rot_angle = arccos(np.dot(prev_dir, dir))
    rot_axis = np.cross(prev_dir, dir)
    rot = Rotation.from_rotvec(rot_axis * (rot_angle / np.linalg.norm(rot_axis)))
    basis = rot.apply(basis)
    rotated_circle = circle @ basis
    positioned_circle = rotated_circle + midpoint(segment)
    results.append(positioned_circle)
2
On

Hint.

Given two tubes to synchronize

$$ p_i = p_0 + \lambda_i \vec v_i + r\left(\vec u_i\cos\theta_i+\vec w_i\sin\theta_i\right),\ \ \phi_i \in (-\pi,\pi],\ \ \ i = 1,2 $$

the trick is to determine a common phase origin. We can accomplish that by determining for which $\phi_0$ two lines from the family intersect , defining thus a phase common origin. We can do this by determining the minimum distance (it should be $0$) between

$$ \cases{ p_1 = p_0 + \lambda_1\vec v_1 + r(\vec u_1\cos 0+ \vec w_1\sin 0)\\ p_2 = p_0 + \lambda_2\vec v_2 + r(\vec v_2\cos\phi_0 +\vec w_2\sin\phi_0) } $$

Calling now $\delta(\lambda_1,\lambda_2,\phi_0) = \|p_1-p_2\|^2$ we can derive the minimum conditions by solving for $(\lambda_1,\lambda_2,\phi_0)$

$$ \cases{ \frac{\partial \delta}{\partial\lambda_1}=0\\ \frac{\partial \delta}{\partial\lambda_2}=0\\ \frac{\partial \delta}{\partial\phi_0}=0 } $$

or

$$ \cases{ (\lambda_1\vec v_1+r\vec u_1-\lambda_2\vec v_2-r(\vec u_2\cos\phi_0+\vec w_2\sin\phi_0))\cdot\vec v_1 = 0\\ (\lambda_1\vec v_1+r\vec u_1-\lambda_2\vec v_2-r(\vec u_2\cos\phi_0+\vec w_2\sin\phi_0))\cdot\vec v_2 = 0\\ (\lambda_1\vec v_1+r\vec u_1-\lambda_2\vec v_2-r(\vec u_2\cos\phi_0+\vec w_2\sin\phi_0))\cdot(-\vec u_2\sin\phi_0+\vec w_2\cos\phi_0) = 0\\ } $$

or

$$ \left(\matrix{\|\vec v_1\|^2&-\vec v_1\cdot\vec v_2\\ \vec v_1\cdot\vec v_2& -\|\vec v_2\|^2}\right)\left(\matrix{\lambda_1\\ \lambda_2}\right)= r\left(\matrix{\vec v_1\cdot\vec u_2&\vec v_1\cdot\vec w_2\\ \vec u_2\cdot\vec v_2& \vec w_2\cdot\vec v_2}\right)\left(\matrix{\cos\phi_0\\ \sin\phi_0}\right)-r\left(\matrix{\vec u_1\cdot\vec v_1\\ \vec u_1\cdot\vec v_2}\right) $$

once calculated $\lambda_1,\lambda_2$ this result is substituted into the last equation remaining a last equation that should be solved for $\phi_0$.

Attached a plot showing the case

$$ \cases{ \vec v_1 = (1,1,1)\ \ \ \cases{\vec u_1 = \left(\frac{1}{\sqrt{2}},-\frac{1}{\sqrt{2}},0\right)\\ \vec w_1 = \left(-\frac{1}{\sqrt{6}},-\frac{1}{\sqrt{6}},\sqrt{\frac 23}\right)}\\ \vec v_2 = (-2,-1,2)\ \ \ \cases{\vec u_2 = \left(\frac{1}{\sqrt{5}},-\frac{1}{\sqrt{5}},0\right)\\ \vec w_2 = \left(-\frac{4}{3\sqrt{5}},-\frac{2}{3\sqrt{5}},-\frac{\sqrt{5}}{3}\right)}\\ p_0 = (0,0,0)\\ r = 2 } $$

In red the lines of common phase and in black their intersection point.

enter image description here

1
On

Geee... if indeed your goal is to draw a tube, why not simply use the right software tool, e.g., Mathematica:

Graphics3D[
 Tube[BSplineCurve[{{1, 1, -1}, {2, 2, 1}, {3, 3, -1}, {3, 4, 1}}], .3]]

enter image description here