0
\$\begingroup\$

I have Lifters that "carry" an object. I want the object to move and rotate based on how those Lifters move. Requirements include:

  • Supporting 1-4 Lifters at any side of the Object (e.g., both on one side, opposite, so on - can't make any guarantees about their positions relative to the object)
  • Supporting any orientation of the Object (e.g. the Object could be flipped on its side, sideways, or upside down; can't make guarantees that Forward is always facing forward, etc)

As such I implemented this by having the object move by setting its velocity to the average of the Lifters' velocity.

Now, if not all of them move (or if there's a disparity in their velocities), I want the object to rotate, like so:

This is done by keeping track of how much an individual lifter has moved in a frame from the Object's position, and calculating the angle via

m_Angle += Vector3.SignedAngle(lastPos - transform.position, currentPos - transform.position).

This "movement angle" I add up for every Lifter and then at the end, Rotate the Object via Transform.Rotate(Vector3.up, m_Angle).

However, this approach has some problems:

  1. Calculations are done along the Y axis and therefore does not angle the Object up or down if the Lifters have different vertical positions,
  2. m_Angle actually overshoots, but I found simply dividing m_Angle by the number of Lifters made it perfectly accurate. I do not know why this works.

Because I want this to rotate the object in "3 dimensions", I have attempted to calculate the "euler angles" of each Lifters' movement via:

Vector3 oldDisplacement = lastPos - transform.position;
Vector3 newDisplacement = currentPos - transform.position;
Quaternion rotation = Quaternion.FromToRotation(oldDisplacement, newDisplacement);
m_EulerAngles += rotation.eulerAngles;

And then at the end, I rotate the Object based on these "movement angles":

transform.Rotate(m_EulerAngles, Space.World);

This works in three dimensions, however it is off in the same way that m_Angle was before I divided it. It rotates the object too far. On top of that it seems to produce rotations along axes that have no displacement.

I want the object to only rotate exactly by the angle created by the Lifter's movement. This should keep it "synced" to Blue instead of rotating past as you see here.

When I check how much rotation Blue is adding per frame, I get an output like so:

Blue is adding (0.000, 359.507, 0.066); on movement (-0.015, 0.000, 0.000)

Despite moving solely on the x-axis, there is a z-euler angle being created, and the y-euler angle of 0.493 degrees is too much for -0.015f movement. I do not know why rotation.eulerAngles is inaccurate in my usage.

The only issue I can perceive is that since the Object's position is also moved at the end, the angle calculation assumes it is not moving and generates a larger angle (e.g., perhaps newDisplacement should be something like currentPos - (transform.position - m_Body.velocity * Time.deltaTime) ). However, removing the Object's movement from the finalPosition of the Lifter seemingly has no effect. Or perhaps there is some finite precision in Quaternion.FromToRotation? Or my math is incorrect in some way?

I'd love a sanity check on the code or approach or if I should approach this problem from a different perspective.

EDIT: Here's an example gif of the functionality with m_EulerAngles code (so the rotation overshoots, but works in 3D - also I recorded the gif at lower fps that's not the game's framerate)

\$\endgroup\$
  • \$\begingroup\$ Have you considered using joints for this purpose? \$\endgroup\$ – DMGregory Jul 7 at 0:27
  • \$\begingroup\$ The images are a little confusing. The capsules are lifters and the two of them together are lifting the desk? \$\endgroup\$ – Kevin Jul 7 at 1:16
  • \$\begingroup\$ @Kevin: Yep. Once they're within x distance of the table they interact and then when they move the table moves with them. If they get too far the table is dropped. It's just placeholder to replaced with first person and third person characters but I wanted the math of the movement to work first. \$\endgroup\$ – Visulth Jul 7 at 6:05
  • \$\begingroup\$ @DMGregory: I did attempt joints but never figured out how to get "multiple" joints to one object to work. Each object can have 1-4 lifters. I also attempted constraints and found it didn't allow any "rigidbody" type motion to affect the object so couldn't work for my use-case. \$\endgroup\$ – Visulth Jul 7 at 6:05
0
\$\begingroup\$

Trying to track displacement/deltas each frame is probably making this more complicated than it needs to be.

A much simpler solution is not to use deltas, and just update current position/rotation each frame based on the lifters.

Here's a simple example:

public class LifterManager : MonoBehaviour {

    [SerializeField] private Transform lifter1;
    [SerializeField] private Transform lifter2;
    [SerializeField] private Transform liftedObject;

    private Quaternion rotationOffset;

    void Start() {
        //get the difference between (the angle between the lifters) 
        //and (lifted object's current rotation). later, we can apply
        //this difference to achieve the desired rotation when the lifters move
        Quaternion rot = GetLifterAngle();
        rotationOffset = liftedObject.rotation * Quaternion.Inverse(rot);
    }

    private void Update() {
        //position at midpoint between our two lifters
        liftedObject.position = (lifter1.position + lifter2.position) / 2f;
        //get the (angle between the lifters) and apply our previously saved offset. 
        //This rotates the lifted object according to the movement of the lifters
        Quaternion rot = GetLifterAngle();
        liftedObject.rotation = rot * rotationOffset;
    }

    private Quaternion GetLifterAngle() {
        return Quaternion.LookRotation(lifter2.position - lifter1.position, Vector3.up);
    }
}

We don't care how the lifters moved since the last frame; we just care where they are currently positioned.

This simplified example doesn't track position offsets (basically, initial distance between each lifter and the point on the object that they grabbed) so the object can sometimes "jump" a bit when lifted. In your final code you'll probably want to keep track of those position offsets so you can maintain the relative distance between each lifter and the point they grabbed.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I did try something like this, if I'm not mistaken, does GetLifterAngle() assume both Lifters will be opposite each other? In my scenario I need to support 1-4 Lifters and they need to be able to move the object from any orientation (e.g., both on one side, or the table flipped on its side / upside down / etc). I can't make any guarantees about the table's orientation or their positions relative to the table. \$\endgroup\$ – Visulth Jul 7 at 6:08
  • \$\begingroup\$ @Visulth Those are critical criteria you did not mention in the original post. My example is designed for the scenario you depicted in your images. \$\endgroup\$ – Kevin Jul 7 at 7:18
0
\$\begingroup\$

One of the major elements I was struggling with in my scenario was the over-rotation from the Quaternion.FromToRotation() method.

I noticed that it gave small angles even when an individual Lifter had not moved (e.g., when From rotation and To rotation were identical). I had focused so much on the moving Lifter I did not notice the non-moving Lifter was adding rotation when it should not have been.

I limited angle calculation and addition to only when an individual Lifter moves and suddenly the rotation is perfectly accurate, and furthermore did not need the questionable division of Lifters count from the vector method.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.