Control Z rotation without interfering with X and Y rotations

Hi,

Imagine a scenario, where a wheel is rooling over a hill, and you want to control the direction (on Y axis), and the acceleration (on X axis), but I need to control the tilt for both sides.
So I want to be able to restrain (or control) the Z rotation axis (which should tending to zero), but allow the X rotate freely and controlled by physics, and the Y axis to be controlled by input.

This is the code I’m using, but it still interferes with X and Y rotation axis.

    void Update()
    {
        Vector3 v = transform.rotation.eulerAngles;
        v.z = 0;
        Quaternion rotation = Quaternion.FromToRotation(transform.rotation.eulerAngles, v);
        float step = speed * Time.deltaTime;
        transform.rotation = Quaternion.RotateTowards(transform.rotation, rotation, step);
    }

Which You think is the best approach to accomplish this behavior ?
Is this even possible, since i want to control an axis that is already been controlled by physics ?

Thanks in advance.

Have you tried using the “Freeze Constraints” options on the Rigidbody? You can prevent individual Axis from being affected by physics, and so you should be able to freely set those rotations from code.

I think @Invertex 's answer is probably the best one for this situation. But if that doesn’t quite suit your needs, or for future situations regarding rotations, consider reading up on Euler angles (and why you should almost never use them).

Well, thank You for your directions.

I think I get a bit closer to the solution…

I’ve constrained the Z axis in the RigidBody, so it won’t be affected by physics.
And stop using euler angles (i’ve read the page :slight_smile: ).

And here is the code I come to:

void Update() {
        // Add torque to X axis (power)
        m_Rigidbody.AddRelativeTorque(new Vector3(moveDirection.z * powerTorqueMultiplier, 0, 0));

        // Add torque to Y axis (turn)
        m_Rigidbody.AddTorque(new Vector3(0, moveDirection.x * turnTorqueMultiplier, 0));

        // Untilt object so it tends to upright position
        Quaternion rotation = Quaternion.FromToRotation(transform.right, Vector3.right);
        transform.rotation = Quaternion.RotateTowards(transform.rotation, rotation, untiltTorqueMultiplier * Time.deltaTime);
}

This work’s fine until i start moving the wheel forward (applying torque in the X axis).

After that, the all object tends to the original position. Not only the Z original value, but also the X and Y values.

This is why i’ve wrongly used euler angles. I thought that changing the Z to 0, I could create a rotation to keep the object upright, and leave the X and Y intact.
Well, I know now, that is not a good idea.

I think I’m close to the solution, but I can’t figure out how to create the correct rotation.

Do You have some other suggestion?

Thank You

I think what you’re going to want to do is to find the current “top” of the wheel, and use that with Vector3.up in your FromToRotation call - otherwise your current strategy seems good. It’s the first step there that is the most challenging, I think, because you obviously can’t just use the local transform.up.

I can’t think of a nice clean way to do it right now, but I can think of an ugly, inefficient way (which might be your best bet): use sin and cos along with transform.TransformPoint (or transform.rotation) to “trace a line” around the rim of the wheel, and see which point has the highest Y component, then use that.

float detectionIntervalDegrees = 15f; //higher numbers = more efficient, but probably more wobbly movement
Vector3 currentHighestPoint = Vector3.zero;
for (float theta = 0f; theta < 360f; theta += detectionIntervalDegrees) {
Vector3 thisPointLocal = new Vector3(0f, Mathf.Sin(theta * Mathf.Deg2Rad), Mathf.Cos(theta * Mathf.Deg2Rad) ); //I assume Z is forward and Y is up on your wheel object
Vector3 thisPointRotated = transform.rotation * thisPointLocal;
if (thisPointRotated.y > currentHighestPoint.y) currentHighestPoint = thisPointRotated;
}
Quaternion rotation = Quaternion.FromToRotation(currentHighestPoint, Vector3.up);

(You could make this more efficient by short circuiting once it sets the highest point and then starts moving back down, as long as you control for the possibility that it may have started on the “way down” of the wheel… but honestly, unless you’ve got dozens of these wheels, it’s not going to be slow enough to notice the difference)

Well, Your suggestion seems correct, and your code is in fact providing “up” point of the wheel.

However, the X and Y parts of the rotation are not being taken in consideration. So, for example, if I rotate in the Y axis (turn left or right), or rotate in the X axis (thrust), these rotation values are not considered. And because of that, the wheel keeps rotating to his original rotation.

But I don’t understand why, because the variable “thisPointRotated” is being calculated with transform.rotation (line 5 in your code).

So, when the rotation variable is calculated in Quaternion.FromToRotation (in line 8 of your code) function, it should have the X and Y values in it.

What am I missing here ?

Also, I’ve tried to use Rigidbody.AddTorque() with the calculated Y and Z, but i’m not getting anywhere also, since AddTorque seems to lock on local Z axis of the object. AddRelativeTorque, seems to prefer local Z axis also …

        Vector3 torqueVector = new Vector3(0, currentHighestPoint.z, currentHighestPoint.y);
        m_Rigidbody.AddTorque(-transform.rotation.z * torqueVector * untiltTorqueMultiplier * Time.deltaTime);

Any more suggestions are wellcome !!!

Doublecheck that the code I posted is working as expected (it was untested). As you say, thisPointRotated should take into account all of the rotation factors. You might try adding some Debug.DrawLine’s to visualize in the scene view what the logic is doing:

for (float theta = 0f; theta < 360f; theta += detectionIntervalDegrees) {
Vector3 thisPointLocal = new Vector3(0f, Mathf.Sin(theta * Mathf.Deg2Rad), Mathf.Cos(theta * Mathf.Deg2Rad) ); //I assume Z is forward and Y is up on your wheel object
Vector3 thisPointRotated = transform.rotation * thisPointLocal;
Debug.DrawLine(transform.position, transform.position + thisPointRotated, Color.yellow);
if (thisPointRotated.y > currentHighestPoint.y) currentHighestPoint = thisPointRotated;
}
Debug.DrawLine(transform.position, transform.position + currentHighestPoint, Color.green);

I’m also having a hard time visualizing what’s going on - would it be possible to record a video of the incorrect behavior? Ideally with the object selected so that the movement gizmo (with the three axes) can be seen.