# What is meant by Blender's "Minimum" twist method/how do I correctly calculate bezier curve twists?

I'm working on a bezier curve implementation in Unity. Until recently, I had no need for roll (the objects that followed the curve always stood upright) but I've since added a roll value to each control point (0-360°, the same way roll is stored in Blender). I'm calculating rotation using global up, which results in nasty twisting when going up/around loops. This is equivalent to Blender's Z-Up twist method; exporting my test track model using Z-Up demonstrates the exact same problem:

Any object that follows the track flips around when it reaches these ugly twists/control points. Blender also has a "Minimum" twist method that produces much better results:

Does anyone know what Blender's "Minimum" twist method is/how to reproduce it in Unity? I'd like to have roll calculated consistently between Blender and Unity.

Attempted Solution #1

I've attempted to solve this issue by computing normals at each control point using the normal of the previous control point. However, my implementation is either not correct or there is something more I need to do to handle loops.

Here is the relevant code:

        public Vector3 ComputeBinormal(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 up, float t)
{
Vector3 tangent = ComputeTangent(p0, p1, p2, p3, t);
return Vector3.Cross(up, tangent).normalized;
}

public Vector3 ComputeNormal(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 up, float t)
{
Vector3 tangent = ComputeTangent(p0, p1, p2, p3, t);
Vector3 binormal = ComputeBinormal(p0, p1, p2, p3, up, t);
return Vector3.Cross(tangent, binormal).normalized;
}

public Vector3 GetNormal(float t)
{
int index = GetCurveIndex(t);
return transform.TransformPoint(ComputeNormal(points[index], points[index + 1], points[index + 2], points[index + 3], Vector3.up, GetCurveValue(t))) - transform.position;
}

public void ResetNormals()
{
normals = new Vector3[(points.Length - 1) / 3 + 1];

normals[0] = Vector3.up;
Vector3 tangent = ComputeTangent(points[0], points[1], points[2], points[3], 0f);
Vector3 binormal = Vector3.Cross(tangent, Vector3.up).normalized;

for (int i = 1, j = 0; i < normals.Length; i++, j += 3)
{
normals[i] = ComputeNormal(points[j], points[j + 1], points[j + 2], points[j + 3], normals[i - 1], 0f);
}
}

public Quaternion GetRotation(float t)
{
// backwards compatibility so you can upgrade projects
if (rolls.Length != modes.Length)
{
return Quaternion.LookRotation(GetVelocity(t));
}
else
{
if (normals.Length != modes.Length)
{
ResetNormals();
}

Quaternion roll1 = Quaternion.Euler(0f, 0f, GetControlPointRoll(GetCurveIndex(t)));
Quaternion roll2 = Quaternion.Euler(0f, 0f, GetControlPointRoll(GetCurveIndex(t) + 4));
return Quaternion.LookRotation(GetVelocity(t), GetControlPointNormal(GetCurveIndex(t))) * Quaternion.Lerp(roll1, roll2, GetCurveValue(t));
}
}



Attempted Solution #2

It's clear that computing normals for just the control points is not sufficient; the normal has to be computed per point on the curve (i.e. each frame a walker moves along the curve). In that case, how would I start a walker at any arbitrary point on the curve, not just at zero?

• Sep 16, 2022 at 11:08
• Note that you don't need to separate your edits with "Edit 2:" headers. Edit history is tracked automatically. So, when editing your question, write it as a whole as though this were the first version you asked. If anyone is curious about which bits were added when, they can always click the "edited" link at the bottom to see the full blow by blow. Sep 17, 2022 at 14:02