Rotate Pivot 90 Degrees Clockwise


Im trying to rotate the torso section of this mech to look at a target. To do this I’d like to use the spine bone to which the top portion of the mech is skinned to. In 3ds Max the bone pivot is structured as follows…
X : Up
Y : Right
Z : Forward

I need to rotate this pivot by 90 degrees in order to match the Unity coordinate system, and I’ve done this as follows…

    private Quaternion InverseBonePivot(Quaternion q)
    {
        float x = q.x;
        float y = q.y;
        float z = q.z;
        float w = q.w;

        q.x = -y;
        q.y = -x;
        q.z = z;
        q.w = w;

        return q;
    }
    private void UpdateGyroRotation()
    {
        Vector3 localTargetPosTurret = transform.InverseTransformPoint(m_TargetGyro.position);
        Quaternion turretRotTarget = Quaternion.LookRotation(localTargetPosTurret);
        Quaternion newRotation = Quaternion.RotateTowards(InverseBonePivot(m_GryoRotLast), turretRotTarget, m_TorsoSpeed * Time.deltaTime);

        float clampedX = ClampAngle(newRotation.eulerAngles.x, m_TorsoMinPitchAngle, m_TorsoMaxPitchAngle);
        float clampedY = ClampAngle(newRotation.eulerAngles.y, m_TorsoMinYawAngle, m_TorsoMaxYawAngle);
        Vector3 remap = new Vector3(clampedX, clampedY, m_GyroHelper.localEulerAngles.z);
        Quaternion q = Quaternion.Euler(remap);

        m_GyroHelper.localRotation = InverseBonePivot(q);
        m_GryoRotLast = m_GyroHelper.localRotation;
    }

I have 2 problems with this code. The first is that the up/down rotation is inversed and the 2nd problem is that the z rotation is staying at 0, hence the torso looks like its starting to roll, when it should be relatively level with the ground. As you can see this is also a problem for the arms.


Could anyone offer some suggestions? I do not want to use the “create an empty gameobject and attach in between trick” because I need the rig to stay in tact so I can compensate the hip height with the terrain for my foot IK system.

Cool mech! I want to crush something with him!!!

I hear you there, but what about doing it actually within the 3DMax model structure, eg, introducing an extra “bone” and reorganizing there? Or would that bust too much of your hierarchy?

I think ideally you want Unity to be the ONLY one doing this, and doing it only once at the model importation standpoint. I’m not even sure what net effect you swizzling a single quaternion would have at the end of the day.

Where are you doing your animations, in Unity?

Another “burn it all down and start over” approach is to make your ultimate “one mech bone hierarchy to rule them all!” structure in Unity, and then go and “hang” the individual pieces of your mech graphics onto this hierarchy. That way you can nail the hierarchy down with just cubes or something else for easy visualization, then hang whatever mech bits you want on it.

First, I’m impressed you attempted to swizzle a Quaternion. Unfortunately, that’s not going to solve your chirality.

The problem you’re facing is that 3DS Max (and Blender and almost all of humanity) is a right-handed universe, while Unity followed Microsoft’s folly into the ridiculous realm of left-handed coordinate systems. There is no possible rotation that will marry these two systems. Model importers do bridge the gap by scaling everything by -X 100% and flipping model normals, not just rotating.

Second, you can surely work out the necessary rotations you need to do. You seem to be relying on RotateTowards, but that’s going to assume a fully free joint that can turn at any angle. You just want a single axis to pivot to rotate to a position as close as possible to a target. If I got that right, then just use Vector3.ProjectOnPlane from your target, using the legal Mech Hips pivot axis as the plane normal argument. Given your first picture, the X axis is the axis you want to allow the Mech to pivot. So Vector3 targetApprox = Vector3.ProjectOnPlane(target.transform.position, mechHipsBone.transform.right); and then use that targetApprox in your RotateTowards instead of the actual target.

(transform.right is just a shorthand for “the X axis” or “the normal of the YZ plane”)

I’m trying to implement what you suggested and I am not really sure which direction I should be projecting from.

        Vector3 targetApprox = Vector3.ProjectOnPlane(m_TargetGyro.position - m_Hips.position, m_Hips.transform.right);
        Quaternion turretRotTarget = Quaternion.LookRotation(targetApprox, m_Hips.transform.right);
        Quaternion newRotation = Quaternion.RotateTowards(m_GryoRotLast, turretRotTarget, m_TorsoSpeed * Time.deltaTime);

        m_GyroHelper.localRotation = newRotation;
        m_GryoRotLast = m_GyroHelper.localRotation;

The “gyro” is the actual part of the mech I want to rotate and is represented by the bone “B_SPINE” in my hierarchy. “B_SPINE” is a child of “B_HIPS”. Attached below are images of the hierarchy setup, with the first image representing the spine bone, the second image as the hips bone and the 3rd image a view of what is happening.

8749068--1185216--image_2023-01-22_152905657.jpg

8749068--1185219--upload_2023-1-22_15-29-24.jpg

8749068--1185222--upload_2023-1-22_15-30-1.jpg

My confusion lies in which direction to use for the rotation, “up”, “right” or “foward”. I tried all 3, but each one just renders a different version of the problem represented in picture #3. I initially tried with the “right” vector because I want to be rotating around the “X” axis as per the pictures, but clearly this isn’t working. This is the problem that initially set me down the rabbit hole of trying to flip the X & Y axis of rotation.

I am not doing the animations in Unity, they are baked from 3ds Max and imported into Unity. I’m starting to think that perhaps the best work-around for now is to use an empty dummy for the hips/spine in the modeling software with some orientation constraints. Hopefully with this approach perhaps I can still move the hips up/down to compensate for terrain height and in the future a recoil system so that the mech turns/twists when firing weapons in the arms.

But again, I really dont want to use makeshift helper objects to help with rotations. I’d rather do the calculations so its an overall cleaner solution rather than a hack job if it is indeed possible.

Okay, the B_SPINE is the thing we’re trying to pivot around its local X. This should be pretty close.

Vector3 targetApprox = Vector3.ProjectOnPlane(m_TargetGyro.position - m_Spine.position, m_Spine.transform.right);
        Quaternion turretRotTarget = Quaternion.LookRotation(targetApprox, -m_Spine.transform.right);
        Quaternion newRotation = Quaternion.RotateTowards(m_GryoRotLast, turretRotTarget, m_TorsoSpeed * Time.deltaTime);

I looked at my tank turret code again, and I do things slightly differently so I can work in local space instead of world space. But again, it looks like you should be pretty close.

If you want to pack up a small demo project, I can debug.

There’s also the idea (since he is a mech after all) of animating it as two separate model hierarchies:

  • Hips and down for the leg / locomotion stuff, do all the foot-IK in there.

  • Above the hips for all the fun looking and tilting and shooting and whatnot.

Then just always drive the upper part to attach to the lower part each frame.

It makes the concept of walking and chewing gum much simpler.

This is much closer! However the X & Y are still inversed. Flipping the euler angles, I managed to fix it when the target is infront of the object, but when behind weird things happen again. Starting to pull hairs on this one haha.

I’ve attached a copy of the project on Google Drive if you’re willing to take a look. I appreciate the help thus far!
https://drive.google.com/file/d/1pCsBWl0E9xcNGkyEXxdSzvNE-tdG0ita/view?usp=sharing

I ended up rebuilding the rig with additional dummies that have orientation/position constraints to the hip and spine bones.


8749209--1185264--upload_2023-1-22_16-59-2.png

H_GYRO, H_LEFTARM_HAND, and H_RIGHTARM_HAND are the dummies I inserted into the rig. Then I applied the constraints onto those objects. As a result the mech torso now rotates correctly, as well as the arms.


Essentially the mech is now 2 individual rigs going off of what Kurt mentioned before. The only catch is that now I will have to translate 2 game objects to move the hips/spine to compensate for terrain angle and height. Its not a terrible trade off I suppose if it means I can move forward with wrapping up the IK system and getting to the fun part.

1 Like

You could’ve done a private message to avoid everyone getting a copy. Now @Kurt-Dekker is going to make a game with your awesome mech!

(Also, when sharing projects, you can nuke the Library/ folder. Saves a lot of space.)

I adapted my turret code to work with local coordinates. This should help.

    private void UpdateGyroRotation()
    {
        Vector3 direction = m_TargetGyro.position - m_GyroHelper.position;
        direction = m_GyroHelper.InverseTransformDirection(direction.normalized);
        direction = Vector3.ProjectOnPlane(direction, m_GyroHelper.transform.up);

        Debug.DrawLine(m_TargetGyro.position, m_GyroHelper.position, Color.red);

        float limit = 90f;
        float angle =
            Vector3.SignedAngle(Quaternion.identity * Vector3.forward,
                direction, Vector3.right);
        angle = Mathf.Clamp(angle, -limit, +limit);

        Quaternion desired = Quaternion.AngleAxis(angle, Vector3.right);
        Quaternion newRotation =
            Quaternion.RotateTowards(m_GryoRotLast, desired,
                m_TorsoSpeed * Time.deltaTime);

        m_GyroHelper.localRotation = newRotation;
        m_GryoRotLast = m_GyroHelper.localRotation;
    }
1 Like

I would never do such a thing without first asking OP!!!

(But yes, it does seem appealing.) :slight_smile:

Eh, I don’t mind if anyone grabs an example off of this. I’m sure someone else will run into a similar problem and I’d like to save that future someone the frustration of trying to figure this out. As for pasting demos, thanks for the tip about the library, wasn’t aware you could do that.

Wow! This definitely sets me on the right track, thank you so much for the help man. If you’re ever in New Jersey, I gotta buy you a drink! The angle clamping also seems much more efficient than what I had originally, so thank you for going the extra step.
8749506--1185321--upload_2023-1-22_21-14-35.jpg

Rotations are something I struggle with none the less, so if you can bear with me here. If I were to add a pitch (up/down) rotation, all I would need to do is duplicate the plane projection and use m_GyroHelper.transform.right, as well as duplicating the desired and new rotation and multiply that result with the one you calculated for yaw, correct?

I’m trying to wrap my head around how the code you posted works exactly. If Im understanding correctly, by projecting on a plane, its almost as if you’re taking the whole world and virtually rotating it so that it is oriented in parallel with the “gyro” object. Then you’re using the signed angle to create a rotation about that plane to align with the target. Is the correct interpretation, or have I missed the mark?
I attempted to create a pitch direction, but I got lost as to how I should be projecting and then which axis I should be rotating about to create the matching rotation. I figured that I would still use the same projection, and then my angle would calculation would be done using “Vector3.up”. However, this does not work when I implement it.

Ok, strike out what I said above. I figured out how to rotate w/ both pitch and yaw.

    private void UpdateGyroRotation()
    {
        //Get Look Direction to Target
        Vector3 direction = m_TargetGyro.position - m_GyroHelper.position;
        Vector3 localDirection = m_GyroHelper.InverseTransformDirection(direction.normalized);

        //Yaw Rotation
        Vector3 yawProjection = Vector3.ProjectOnPlane(localDirection, m_GyroHelper.transform.up);

        float limitYaw = 90f;
        float angleYaw = Vector3.SignedAngle(Quaternion.identity * Vector3.forward, yawProjection, Vector3.right);
        angleYaw = Mathf.Clamp(angleYaw, -limitYaw, +limitYaw);

        Quaternion desiredYaw = Quaternion.AngleAxis(angleYaw, Vector3.right);


        //Pitch Rotation
        Vector3 pitchProjection = Vector3.ProjectOnPlane(localDirection, m_GyroHelper.transform.right);

        float limitPitch = 20f;
        float anglePitch = Vector3.SignedAngle(Quaternion.identity * Vector3.forward, pitchProjection, Vector3.up);
        anglePitch = Mathf.Clamp(anglePitch, -limitPitch, +limitPitch);

        Quaternion desiredPitch = Quaternion.AngleAxis(anglePitch, Vector3.up);

        Quaternion newRotation = Quaternion.RotateTowards(m_GryoRotLast, desiredYaw * desiredPitch, m_TorsoSpeed * Time.deltaTime);

        m_GyroHelper.localRotation = newRotation;
        m_GryoRotLast = m_GyroHelper.localRotation;
    }

The last thing I’d like to do before calling this done, is to nullify the z rotation component so that the torso does not roll. How could I reasonably go about this using the current implementation? I suppose that would involve just getting the difference in angle between the hips and spine and using that as a correction for the z component?

The figure above demonstrates the current implementation. The figure below is what I would like to achieve (manually rotated the spine for demonstration purposes)… Essentially keeping the gyro level relative to the hips.

Hm, I think the key there would be to decide just what plane/axis you’re using for the rotation. It doesn’t have to be the plane/axis of one bone. It could be a mix of two bones, or Vector3.up instead. Play around with it. I think you’re on the right track.

1 Like