0

Suppose I have the center of the circle c=[x0, y0, z0], the radius of the circle r, and the normal to the circle n=[a, b, c]. The general equation of a circle in 3D space is:

((x - x0)^2 + (y - y0)^2 + (z - z0)^2 - r^2)^2 + (a(x - x0) + b(y - y0) + c(z - z0))^2 = 0

for example:

r=20
n = [1, 1.5, 1]
c = [2, 3, 4]

How to draw the the circle in python? I want the dots on the circle are equally distributed with a step size of theta.

theta = 1 # in degree
f. c.
  • 1,095
  • 11
  • 26
  • Pick a plotting library that does 3D plots of spheres. For example [Plotting a 3D cube, a sphere and a vector in Matplotlib](https://stackoverflow.com/questions/11140163/plotting-a-3d-cube-a-sphere-and-a-vector-in-matplotlib), which shows how to create an `np.mgrid` for a sphere parametrically, then does `ax.plot_surface()` – smci Jan 14 '22 at 06:04
  • @smci I do not think it is duplicated. I am asking to draw a circle, not a sphere. they are different. A circle has a direction defined by its normal, but sphere does not. The problem is I need to sample the circle with a uniform size of `theta`. Still trying to figure out how to do it. I will update the question to make it more clear. – f. c. Jan 14 '22 at 06:26
  • 1
    Oh you only want a circle. In 3D. – smci Jan 14 '22 at 07:59

2 Answers2

1

You can easily do it using some trig. If we get the point on a unit circle, we can just scale it by the radius of the desired circle. This is because the radius of the unit circle is 1, hence being called a unit circle.

With Soh Cah Toa, we know sine will give us the opposite side of a triangle, our Y, and cosine will give us the adjacent side, our X. So to get a point at θ radians, we just need to do

import math
radius = 20
theta = math.pi
point = (math.cos(theta)*radius, math.sin(theta)*radius)
print(point)
"""(-20.0, 0.0) """

Since you want it to use degrees not radians, do a quick conversion of your degrees to radians beforehand by multiplying by π/180. From that point, you can just use a for loop and increase the angle you get the point at by theta amount each time. This will give you evenly spaced points.

ZXYNINE
  • 712
  • 4
  • 5
  • I am asking to draw a circle in 3D, and it needs to do some space transformation to make it work. I have figured out how to do it, but still thanks for sharing your thoughts. – f. c. Jan 14 '22 at 11:19
0

This function should fulfil your purpose, with a minor alteration required if you wish to use step_size instead of npoints (they'll be evenly distributed either way).

import matplotlib.pyplot as plt
import numpy as np

def plot_circle_3d(
    ax: plt.Axes,
    center: tuple[float, float, float],
    normal: tuple[float, float, float],
    radius: float,
    npoints: int,
    **kwargs
) -> None:
    t = np.linspace(start=0, stop=2 * np.pi, num=npoints)
    flat_circle = np.array(
        [radius * np.cos(t), radius * np.sin(t), np.zeros(np.shape(t))]
    )

    if np.isclose(normal[0], 0) and np.isclose(normal[1], 0):
        # Transform matrix breaks if normal vector is [0, 0, n]
        tilted_circle = flat_circle
    else:
        Nt = _calculate_normal_transform(normal=normal)
        tilted_circle = np.matmul(Nt, flat_circle)

    circle = tilted_circle + np.expand_dims(center, axis=1)

    ax.plot(xs=circle[0], ys=circle[1], zs=circle[2], **kwargs)


def _calculate_normal_transform(normal: tuple[float, float, float]) -> np.ndarray:
    x, y, z = normal

    a = np.sqrt(x**2 + y**2)
    b = np.sqrt(x**2 + y**2 + z**2)

    return np.array(
        [
            [
                (x**2 * z + y**2 * b) / (a**2 * b),
                x * y * (z - b) / (a**2 * b),
                x / b,
            ],
            [
                x * y * (z - b) / (a**2 * b),
                (x**2 * b + y**2 * z) / (a**2 * b),
                y / b,
            ],
            [-x / b, -y / b, z / b],
        ]
    )

fig = plt.figure()
ax = fig.add_subplot(projection="3d")

plot_circle_3d(ax=ax, center=(2, 5, 7), normal=(1, 2, 1), radius=5, npoints=1000)

plt.show()

3d circle

MattW
  • 1
  • 2