I have an application where it is important to check if a 3D polyline revolve fully around an axis. Here's two example where the polylines do revolve around Oz:

And here is an example where it does not:

One constraint I have that all polylines are connected (i.e the first and the last vertex is the same). Any ideas? Thanks.
Another option is to rotate and translate the polyline so that the axis passes through origin, and is parallel to the $z$ axis. Then, this simplifies to the classic 2D point-in-polygon test.
I personally prefer the winding number check using "octants": $$\begin{array}{c|c} \text{Rule} & \text{If True} \\ \hline \Delta x \gt 0 & +1 \\ \Delta x \lt 0 & +2 \\ \Delta y \gt 0 & +3 \\ \Delta y \lt 0 & +6 \\ \lvert \Delta x \rvert \lt \lvert \Delta y \rvert & +9 \\ \lvert \Delta x \rvert \gt \lvert \Delta y \rvert & +18 \\ \end{array} \quad \begin{array}{c|c} \text{Sum} & \text{Octant} \\ \hline 19 & 0 \\ 4 & 1 \\ 12 & 2 \\ 5 & 3 \\ 20 & 4 \\ 8 & 5 \\ 15 & 6 \\ 7 & 7 \\ \end{array} ~ \begin{array}{c|c} \text{Sum} & \text{Octant} \\ \hline 22 & 0 \\ 13 & 1 \\ 14 & 2 \\ 23 & 3 \\ 26 & 4 \\ 17 & 5 \\ 16 & 6 \\ 25 & 7 \\ \end{array}$$ Sum $0$ indicates origin, and all other sums are impossible. Here is an illustration of the octants and the sums using the above rules:
Note that odd octants include a diagonal, and even octants an axis. This is one way to ensure the below winding counting handles nontrivial cases correctly.
Let $o_i$ be the octant for vertex $i$ in the polyline with respect to the axis, for $i = 0, \dots\ n-1$, and for simplicity, $o_n = o_0$ for the repeated closing vertex. Then, the winding number $w$ for the polyline is $$w = \sum_{i=0}^{n-1} (((12 + o_{i+1} - o_{i}) \mod 8) - 4)$$ except for the cases where $o_{i+1} - o_{i} = \pm 4$, which are handled separately:
Calculate $$\begin{aligned} \Delta x &= x_{i+1} - x_{i} \\ \Delta y &= y_{i+1} - y_{i} \\ \end{aligned}$$ noting that $x_{i}$, $y_{i}$, $x_{i+1}$, and $y_{i+1}$ do not necessarily need to have the axis at origin, as long as they are Cartesian coordinates in a plane perpendicular to the axis. Usually they'll be with the axis at origin, though.
If $\lvert \Delta x \lvert \ge \lvert \Delta y \rvert$, then calculate $$\delta y = y_{i} x_{i+1} - x_{i} y_{i+1}$$ (The line segment intersects $x = 0$ at $y = \delta y / \Delta x$.)
If $\delta y = 0$, the polyline passes through the axis.
If $\delta y \gt 0$, subtract $4$ from $w$.
If $\delta y \lt 0$, add $4$ to $w$.
Otherwise, calculate $$\delta x = x_{i} y_{i+1} - y_{i} x_{i+1}$$ (The line segment intersects $y = 0$ at $x = \delta x / \Delta y$.)
If $\delta x = 0$, the polyline passes through the axis.
If $\delta x \gt 0$, add $4$ to $w$.
If $\delta x \lt 0$, subtract $4$ from $w$.
The expression $((12 + o_{i+1} - o_{i})\mod 8) - 4$ just maps the difference to range $-4$ to $+3$, inclusive: $$\begin{array}{c|c} \text{difference} & \text{value} \\ \hline -7 & 1 \\ -6 & 2 \\ -5 & 3 \\ -4 & \text{special} \\ -3 & -3 \\ -2 & -2 \\ -1 & -1 \\ 0 & 0 \\ 1 & 1 \\ 2 & 2 \\ 3 & 3 \\ 4 & \text{special} \\ 5 & -3 \\ 6 & -2 \\ 7 & -1 \\ \end{array}$$ In Python and C and many other languages, with
o0as $o_i$ ando1as $o_{i+1}$, you can use((12 + o0 - o1) & 7) - 4.For a closed polyline, $w = 8 k$, where $k \in \mathbb{Z}$.
If $w = 0$, axis is outside the polyline.
If $w = \pm 8$, axis in inside the polyline.
Otherwise, $w = \pm 16, \pm 24, \pm 32, \dots$, the polyline loops around the axis more than once. Depending on the situation, you probably wish to consider the axis "inside" the polyline in these cases. (In 2D, there is a choice: nonzero considers the axis to be inside the polyline in those cases; odd-even considers the axis to be inside the polyline if $w/8$ is odd, and outside if even. But this does not really apply to the 3D polyline.)
I prefer this approach over the intersection counting approach, because this tends to be faster for longer polylines (more vertices) and easier to implement in a numerically robust way, as there is only one "special case" (when a line segment passes through four octants), and that too has a simple solution with just additions, subtractions, multiplications, and comparisons.
Here is an example Python 3 implementation:
You can run it for example via
which will output
because the Z axis at origin is inside the octagon. You can also import it, in which case it exports four functions:
winding(points [, last_point):Returns the winding number (in octants) for the 2D polygon with vertices in
points. Normally,pointsis a list or a tuple (of tuples), but if thelast_pointis specified (as the last point in the sequence), thenpointcan be a generator/iterator.winding3d(points, axis_point, axis_direction):Returns the winding number (in octants) for the 3D polygon with vertices in
points, with respect to the axis specified by direction (axis_direction) and a point on the axis (axis_point). The direction does not need to be an unit vector.octant(dx, dy)Returns the octant as specified in this answer, from $0$ to $7$, inclusive, or
Noneifdx==0anddy==0(or if there is a bug in the implementation, although light testing indicates it should be correct).rotation_matrix(to_z_axis)Returns a tuple of tuples describing a 3×3 rotation matrix that rotates
to_z_axisparallel to the (positive) $z$ axis.