Correct normals on a cylindrical rail (hairy ball theorem)

According to the Hairy Ball theorem, it is not possible to get consistently oriented normals at any point on the curve.

What I want to achieve is a game object moving beneath a procedurally-generated rail (plain cylinder). To generate a tube I use a spline which consists of bezier curves. Thus, I need to get a correctly oriented normals at any point of the curve.

The code looks like this:

Vector3 q2 = GetCurveTangent (...);

Quaternion lookAt = Quaternion.LookRotation (q2);
Vector3 normal = lookAt * Vector3.up;
normal = Quaternion.AngleAxis (angle, q2) * normal;
return point + normal * size;

Which generates the following normals:

On horizontal segments everything is correct, but on vertical ones, when tangent is oriented like Vector3.up, everything goes wrong.

The only advice I’ve received is to try and use the previous tangent instead of global up vector. But I can not get any good result out of it. What I tried to do is:

Quaternion lookAt = Quaternion.LookRotation (q2, previousNormal);

but it is not clear what to place here:

Vector3 normal = lookAt * ?

And any combination of tangent, normal and rotation gives me something like this:

So, the question is: how can one create a cylindrical rail and an object moving beneath it (using the normal facing strictly down the curve)? If curve is vertical, “down” is of course globally forward and so on.

you can try something like this. I wrote this code according to my own purposes and it works on CatMull. You can also generate it while the curve points are been generating. First, take derivative of the spline that you use. Secondly apply for all generated point, cross product, in your case it will be Vector3.cross(Vector3.up, Derivative of any spline at time T (U)).

public Vector3 FindSplineNormal(Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float U)
{   //SplineNormals
    Vector3 NormalUp = Vector3.Cross(Vector3.up, (0.5f) * (Mathf.Pow(U, 2) * 3 * (-P0 + 3 * P1 - 3 * P2 + P3) + 2 * U * (2 * P0 - 5 * P1 + 4 * P2 - P3) + (-P0 + P2))).normalized;
    return NormalUp;