0
\$\begingroup\$

I have two objects in my scene each at different positions and rotations in the scene. In this example, object A (the leader) moves according to controls. I need object B (follower) to follow the leader's movement, but in its own space, and I'm having a ton of trouble with it.

For example, if the Leader moves 1 unit in its forward direction, I want the follower to move one unit in its own forward direction even if the two are facing different ways. The same goes for the other axes as well (Y/up/down works fine though).

I have tried seemingly every combination of Transform.Translate on the follower, but still, the forward/back/left/right axes get mixed up. It moves correctly when they're both facing the same way, but when the rotation changes (particularly on the Y-axis) things get messy. Here's my code at the moment. Any help would be appreciated:

        private void SetStartPos()
        {
            // these are two cached Vector3 variables
            startPos = leader.localPosition;
            startRot = leader.eulerAngles;
        }

        private void Update()
        {
            Vector3 posOffset = leader.localPosition - startPos;
            Vector3 rotOffset = leader.eulerAngles - startRot;

            SetStartPos();

            // because this runs in update, this check just prevents huge 
            // instant offset glitches at the beginning.
            if (posOffset.x > 3 || posOffset.y > 3 || posOffset.z > 3 || rotOffset.y > 5f)
                return;

            follower.transform.eulerAngles += rotOffset;
            follower.transform.Translate(posOffset, Space.Self);
        }

To clarify, if needed, the goal is to get them to move by the same amount in the same direction relative to themselves. For some reference, this is being used for matching an AR device's 3D position to the position of a GameObject being streamed over a local network for a basic tracking setup.

Thank you very much in advance!

UPDATE: After doing some more digging, I did find a solution that works for the tracking/following, but there seems to be an issue where it immediately jumps to a seemingly arbitrary position and rotation when it begins. And, if I disable track, then reenable it, the follower will snap to the rotation of the leader, rather than continuing from it's current rotation as needed.

        private Vector3 leaderStart, followerStart;
        private Quaternion rotationDiff;
        private bool isFirstTrack = false;
        private bool track = true;

        private void SetStartPos()
        {
            leaderStart = leader.localPosition;
            followerStart = follower.position;
        }

        void CalculateObjectDiff()
        {
            followerStart = follower.position;
            Quaternion origYRot = Quaternion.Euler(0, follower.rotation.eulerAngles.y, 0);
            Quaternion origProxyRot = Quaternion.Euler(0, leader.rotation.eulerAngles.y, 0);
            rotationDiff = origYRot * Quaternion.Inverse(origProxyRot);
        }

        private void Update()
        {
            if (!track)
                return;

            if (isFirstTrack)
            {
                SetStartPos();
                CalculateObjectDiff();
                isFirstTrack = false;
                return;
            }

            follower.position = followerStart + rotationDiff * (leader.position - leaderStart);
            follower.rotation = rotationDiff * leader.rotation;
        }

\$\endgroup\$
2
  • \$\begingroup\$ Did you consider resetting isFirstTrack when changing track from false to true? \$\endgroup\$
    – DMGregory
    Feb 27, 2022 at 1:28
  • \$\begingroup\$ @DMGregory Yup, still jumps. The rotation issue can be ironed out with some tinkering, but I have no idea what's causing it to set the position to far away from its starting position. \$\endgroup\$ Feb 27, 2022 at 2:13

2 Answers 2

1
\$\begingroup\$

As a general rule, don't try to add and subtract rotations with Euler angles. Unless you're working with something restricted like an FPS camera, prefer quaternions for rotation calculations.

public class LocalMimic : MonoBehaviour
{
    public Transform leader;

    IEnumerator Start() {
        Quaternion lastOrientation;
        Vector3 lastPosition;

        while (true) {
            // Cache the leader's transformation.
            lastOrientation = leader.rotation;
            lastPosition = leader.position;

            // Wait till next frame.
            yield return null;

            // Compute leader's local transformation change since last frame.                 
            Quaternion undoOrientation = Quaternion.Inverse(lastOrientation);
            Quaternion localRotationChange = undoOrientation * leader.rotation;

            Vector3 worldTranslation = leader.position - lastPosition;
            Vector3 localTranslation = undoOrientation * worldTranslation;

            // Apply that same local change in our own local space.
            transform.Translate(localTranslation);                
            transform.rotation = transform.rotation * localRotationChange;
        }
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ Thanks so much for the suggestion. With this code I'm getting a strange behavior where it almost seems too sensitive? Like a small change in the leader results in a more significant change in the follower. Also, this functionality needs to be put on "pause" or only execute in certain situations. In my original code, I put a guard clause in the update (omitted here for clarity). When I try to start the coroutine the follower jumps to some seemingly random position, rather than "continuing" motion from its starting position/rotation. If that makes sense... \$\endgroup\$ Feb 27, 2022 at 0:03
  • \$\begingroup\$ I'm not able to reproduce those issues, so I'll need you to edit your question to include a Minimal Complete Verifiable Example. Once I can reproduce the problem, I can test potential fixes. Something to check for: do you have non-unit scales applied to any of these objects or their parents? \$\endgroup\$
    – DMGregory
    Feb 27, 2022 at 0:11
  • \$\begingroup\$ I don't believe so, no. I actually found a solution that works a bit better for me, but still the issue of it jumping at the beginning. I will update the question with this new info. \$\endgroup\$ Feb 27, 2022 at 0:17
0
\$\begingroup\$

To answer my own question after doing much research and help from other devs: The trick was to set the leader's position (and rotation if needed) to zero before beginning the tracking. This prevents any jumping and rotational issues. In my use-case this works perfectly, but for other situations where the leader object shouldn't be set to (0,0,0) then you just want to reset it to its starting position as shown in the code below.

    private Vector3 leaderStart, followerStart;
    private Quaternion rotationDiff;
    private bool isFirstTrack = false;
    private bool track = true;

    private void SetStartPos()
    {
        leaderStart = leader.localPosition;
        followerStart = follower.position;
    }

    void CalculateObjectDiff()
    {
        followerStart = follower.position;
        Quaternion origYRot = Quaternion.Euler(0, follower.rotation.eulerAngles.y, 0);
        Quaternion origProxyRot = Quaternion.Euler(0, leader.rotation.eulerAngles.y, 0);
        rotationDiff = origYRot * Quaternion.Inverse(origProxyRot);
    }

    private void Update()
    {
        if (!track)
            return;

        if (isFirstTrack)
        {
            leader.position = Vector3.zero;
            SetStartPos();
            CalculateObjectDiff();
            isFirstTrack = false;
            return;
        }

        follower.position = followerStart + rotationDiff * (leader.position - leaderStart);
        follower.rotation = rotationDiff * leader.rotation;
    }
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .