Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Calculating rotation basing on two given vectors

Discussion in 'Scripting' started by neco777, Dec 18, 2020.

  1. neco777

    neco777

    Joined:
    Aug 30, 2014
    Posts:
    6
    The problem is not really Unity related, it is more about 3D math which I do not really understand. It comes from my hobby project where I have accelerometer and magnetometer data coming to my app and I want to make a controller basing on these two pieces of information.

    I simplified the problem in the way it is all in one script and no devices needed.
    So let's imagine we have two objects (cubes) in the scene. One cube is source of movement, the second should repeat them. But the only way the position can be transferred is two vectors.
    First vector is gravity, second is magnetic field. These two vectors are not collinear (and this makes me think the puzzle is solveable). In my place magnetic field is 28 degrees to south.

    When these two vectors are projected to the source cube it gives us two vectors that should be used to calculate rotation for the second cube.

    upload_2020-12-19_2-44-47.png

    Here is the code for the source cube:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SampleLogic : MonoBehaviour
    6. {
    7.     public GameObject MimicObject; // <-- should be set to the destination cube
    8.  
    9.     private GameObject MagneticField;
    10.  
    11.     private float MagneticForce = 1200;
    12.     private float AccelerationForce = 4000;
    13.  
    14.     void Start()
    15.     {
    16.         var magField = new GameObject();
    17.         magField.transform.Rotate(Vector3.forward, 28);
    18.         MagneticField = magField;
    19.     }
    20.  
    21.     void Update()
    22.     {
    23.         var acc = Vector3.up * AccelerationForce;
    24.         var nacc = transform.localToWorldMatrix.MultiplyVector(acc).normalized;
    25.  
    26.         Debug.DrawLine(Vector3.zero, nacc, Color.yellow);
    27.  
    28.         var mag = Vector3.up * MagneticForce;
    29.         var magTmp = MagneticField.transform.localToWorldMatrix.MultiplyVector(mag).normalized;
    30.         var magOnCube = transform.worldToLocalMatrix.MultiplyVector(magTmp).normalized;
    31.  
    32.         Debug.DrawLine(Vector3.zero, magOnCube, Color.green);
    33.  
    34.         ApplyToObject(nacc, magOnCube, MimicObject);
    35.     }
    36.  
    37.     private void ApplyToObject(Vector3 acc, Vector3 mag, GameObject target) {
    38.         var lookAtVector = new Vector3(1, 0, 0); // what should be here???
    39.  
    40.         target.transform.LookAt(lookAtVector,  acc);
    41.     }
    42. }
    43.  
    The question is on line 38 (marked with the comment).
    I spent about three days (nights mostly) trying to make it working on my physical device, but it only works in some parts of the inputs and when I rotate something to 180 degrees it becomes completely wrong.

    As I see the key is that that magnetic vector is relative to the magnetic force field so I need to mathematically invert the multiplication:
    Code (CSharp):
    1. var magOnCube = transform.worldToLocalMatrix.MultiplyVector(magTmp).normalized;
    Like if it was v1 = Matrix1 * v2 and I needed Matrix1 = f(v1, v1). But in this case I do not need the whole matrix but only its rotation part. I somehow feel it should be possible except maybe the deadzone (when magnetic field is collinear to one of the source cube axis).
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    To match rotations, I would think you could essentially just use Quaterion.FromToRotation(referencePoint.currentDirection, referencePoint.originalDirection)

    (Though then I suppose you'd need to do something with the second reference point to determine your "roll" relative to the first one...)
     
  3. neco777

    neco777

    Joined:
    Aug 30, 2014
    Posts:
    6
    I guess i tried FromToRotation at some point. The idea was to copy the rotation from one side and move it to another. But maybe I did some stupid mistake. I will try again. Thanks!
     
  4. neco777

    neco777

    Joined:
    Aug 30, 2014
    Posts:
    6
    Well, I do not understand how to use FromToRotation here.

    What I realized is that I need to recover XYZ vectors from the magnetometer vector that I have.
    Y in this case is the same as gravity direction.
    X and Z are clearly connected to the magnetometer vector and can be calculated basing on it.

    For now I came up with a casual trigonometrical solution which of course fails on mutiple edge cases (e.g. when something crosses PI/2 border). I believe I can tune it with some specials checks although I do not really like it. The most important part here is just to understand it is technically possible.

    Code (CSharp):
    1.     private void ApplyToObject(Vector3 acc, Vector3 mag, GameObject target) {
    2.         var xzProj = Vector3.ProjectOnPlane(mag, Vector3.up);
    3.  
    4.         if (xzProj.magnitude < float.Epsilon) {
    5.             Debug.LogWarning("deadzone");
    6.             return;
    7.         }
    8.  
    9.         float angleProjAndX, angleProjAndZ;
    10.         if (Mathf.Abs(mag.x) > float.Epsilon) {
    11.             angleProjAndX = Mathf.Acos(mag.x / xzProj.magnitude);
    12.             angleProjAndZ = angleProjAndX - Mathf.PI / 2;
    13.         } else {
    14.             // we do not check mag.z is ~zero as it has to be non-zero in case xzProj > 0 and x ~ 0
    15.             angleProjAndZ = Mathf.Acos(mag.z / xzProj.magnitude);
    16.             angleProjAndX = angleProjAndZ - Mathf.PI / 2;
    17.         }
    18.  
    19.         var rlMag = MagneticField.transform.localToWorldMatrix.MultiplyVector(Vector3.up).normalized;
    20.         var xzProgRlMag = Vector3.ProjectOnPlane(rlMag, acc);
    21.  
    22.         var rlx = Quaternion.AngleAxis(Mathf.Rad2Deg * angleProjAndX, acc) * xzProgRlMag;
    23.         var rlz = Quaternion.AngleAxis(Mathf.Rad2Deg * angleProjAndZ, acc) * xzProgRlMag;
    24.  
    25.         Debug.DrawLine(transform.position, transform.position + xzProgRlMag.normalized, Color.yellow);
    26.         Debug.DrawLine(transform.position, transform.position + rlx.normalized, Color.magenta);
    27.         Debug.DrawLine(transform.position, transform.position + rlz.normalized, Color.cyan);
    28.  
    29.         var lookAtVector = rlz;
    30.  
    31.         target.transform.LookAt(lookAtVector,  acc);
    32.     }
    33.  
    Still looking for a robust (and simple) vector-based solution...
     
  5. neco777

    neco777

    Joined:
    Aug 30, 2014
    Posts:
    6
    Oh, I solved the problem in that trigonometrical way and during tests on real device found my initial model was not correct. In fact I do not know the Up vector because accelerometer does not detect rotation. It can only detect inclination and I need to calculate the Up vector myself. So I have even fewer data than I thought.
    The correct input simulation (as I currently think):
    Code (CSharp):
    1.     void Update()
    2.     {
    3.         var acc = Vector3.up * AccelerationForce;
    4.  
    5.         var cubeFwdAcc = transform.localToWorldMatrix.MultiplyVector(Vector3.back);
    6.         var cubeRightAcc = transform.localToWorldMatrix.MultiplyVector(Vector3.left);
    7.         var cubeUpAcc = transform.localToWorldMatrix.MultiplyVector(Vector3.up);
    8.  
    9.         var projFwdAcc = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeFwdAcc, acc));
    10.         var projRightAcc = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeRightAcc, acc));
    11.         var projUpAcc = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeUpAcc, acc));
    12.  
    13.         var nacc = new Vector3 (
    14.             projRightAcc.y,
    15.             projUpAcc.y,
    16.             projFwdAcc.y
    17.         );
    18.  
    19.         Debug.DrawLine(Vector3.zero, nacc, Color.yellow);
    20.  
    21.         var mag = Vector3.up * MagneticForce;
    22.         var magVector = MagneticField.transform.localToWorldMatrix.MultiplyVector(mag).normalized;
    23.  
    24.         var cubeFwd = transform.localToWorldMatrix.MultiplyVector(Vector3.back);
    25.         var cubeRight = transform.localToWorldMatrix.MultiplyVector(Vector3.left);
    26.         var cubeUp = transform.localToWorldMatrix.MultiplyVector(Vector3.up);
    27.  
    28.         //Debug.DrawLine(Vector3.zero, cubeFwd, Color.green);
    29.         //Debug.DrawLine(Vector3.zero, cubeRight, Color.white);
    30.         //Debug.DrawLine(Vector3.zero, cubeUp, Color.magenta);
    31.  
    32.         var projFwdMag = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeFwd, magVector));
    33.         var projRightMag = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeRight, magVector));
    34.         var projUpMag = MagneticField.transform.worldToLocalMatrix.MultiplyVector(Vector3.Project(cubeUp, magVector));
    35.  
    36.         //Debug.DrawLine(Vector3.zero, projFwd, Color.yellow);
    37.         //Debug.DrawLine(Vector3.zero, projRight, Color.gray);
    38.         //Debug.DrawLine(Vector3.zero, projUp, Color.cyan);
    39.  
    40.         var magOnCube = new Vector3(
    41.             projRightMag.y,
    42.             projUpMag.y,
    43.             projFwdMag.y
    44.         );
    45.         //var magOnCube = transform.worldToLocalMatrix.MultiplyVector(magVector).normalized;
    46.  
    47.         Debug.DrawLine(transform.position, transform.position + nacc, Color.red);
    48.         Debug.DrawLine(transform.position, transform.position + magOnCube, Color.green);
    49.  
    50.         ApplyToObject(nacc, magOnCube, MimicObject);
    51.     }
    52.  
     
  6. neco777

    neco777

    Joined:
    Aug 30, 2014
    Posts:
    6
    Alright, this is the function i needed:
    Code (CSharp):
    1.     private void ApplyToObject2(Vector3 acc, Vector3 mag, GameObject target) {
    2.         var cmag = mag;
    3.  
    4.         var rotation = Quaternion.FromToRotation(Vector3.up, acc);
    5.         var rotatedRight = rotation * Vector3.right;
    6.  
    7.         var cross = Vector3.Cross(acc, cmag).normalized;
    8.         var cross2 = Vector3.Cross(cross, acc).normalized;
    9.  
    10.         Debug.DrawLine(transform.position, transform.position + cross, Color.white);
    11.         Debug.DrawLine(transform.position, transform.position + cross2, Color.yellow);
    12.         Debug.DrawLine(transform.position, transform.position + rotatedRight, Color.magenta);
    13.  
    14.         var vertRotation = Quaternion.FromToRotation(rotatedRight, cross2);
    15.         var vertAngle = Vector3.SignedAngle(rotatedRight, cross2, Vector3.up);
    16.  
    17.         target.transform.rotation = rotation;
    18.  
    19.         target.transform.Rotate(Vector3.up, -vertAngle, Space.World);
    20.     }
    21.