I wanted to investigate a bit of this myself – the upside down camera is a good fix, but I’m making a rail shooter that has limited camera movement, so having another camera control that essentially does the same thing but with a flipped axis seems wasteful. TL;DR I’m in the same boat.
What I found is that you can roll all the way to 180 in a straight line, so the issue is not being upside down by itself, so that’s great. When the track starts going vertical and there’s a potential to be upside down, we start to have problems.
I think there might be a way to “cheat” if you set the roll to 180 as the track reaches the upward point of the loop and begins to head backwards, but you get a bunch of spins that you especially don’t want if a character is meant to be touching “ground”

I looked into how the pathing is done, and the issues are from EvaluateOrientation and EvaluateTangent in CinemachineSmoothPath (at least, that’s because it’s the script used when I generate a cart with a track).
EvaluateTangent takes in a point on a curve and gets the bezier tangent from the curve at that point and turns that into a worldspace direction and returns it.
EvaluateOrientation uses that to find the “forward” direction at the same point. Then it orients the track according to this information and the “up” direction it finds from the transform.
I think it narrows it down to any combination of these things, but I haven’t dug very deep and I’m not so great at the geometry involved:
- BezierTangent3 is not taking into
account the local up direction
- The result from transform.TransformDirection does not provide enough info on the local up vector for EvaluateOrientation to do its job correctly
- The operations in the if (!.fwd.AlmostZero()) block are ending up defaulting to the global Vector.up even though the local transformation is used.
Theoretically, I think this is solvable. We shouldn’t be losing a degree of freedom by climbing vertically because rotations are represented as Quaternions, which prevent gimbal lock when applying transformations to vectors. My hunch is that the code self-imposes a gimbal lock on accident when the previous point’s “up” becomes the next point’s “forward” in climbing vertically - and then rights itself by using the global up.
If I ever get a grip on how to fix this I’ll update.
,I wanted to investigate a bit of this myself.
You can roll all the way to 180 in a straight line, so the issue is not being upside down by itself, so that’s great.
I think there might be a way to “cheat” if you set the roll to 180 as the track reaches the upward point of the loop and begins to head backwards, but you get a bunch of spins that you especially don’t want if a character is meant to be touching “ground”

I looked into how the pathing is done, and the issues are from EvaluateOrientation and EvaluateTangent in CinemachineSmoothPath (at least, that’s because it’s the script used when I generate a cart with a track).
EvaluateTangent takes in a point on a curve and gets the bezier tangent from the curve at that point and turns that into a worldspace direction and returns it.
EvaluateOrientation uses that to find the “forward” direction at the same point. Then it orients the track according to this information and the “up” direction it finds from the transform.
I think it narrows it down to any combination of these things, but I haven’t dug very deep and I’m not so great at the geometry involved:
-BezierTangent3 is not taking into account the local up direction
-The result from transform.TransformDirection does not provide enough info on the local up vector for EvaluateOrientation to do its job correctly
-The operations in the if (!.fwd.AlmostZero()) block are ending up defaulting to the global Vector.up even though the local transformation is used.
If I ever get a grip on how to fix this I’ll update this.