Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Simulation of a moving head (Two axis gimbal)

Discussion in 'Scripting' started by shimmr, May 30, 2023.

  1. shimmr

    shimmr

    Joined:
    May 11, 2023
    Posts:
    3
    Dear Mathmagicians,

    I am working on a project, where I'm using Unity for realtime data processing of an interactive installation in real life. Part of this is simulating a Moving Head (a light fixture used in stage ligthing). Here is the basic idea:
    - The virtual moving head consists of two object, one for the pan axis and one for the tilt axis
    - The virtual moving head is positioned just like in the real world setup
    - The virtual moving head follows a virtual target
    - The virtual moving head pan and tilt rotation is then converted to DMX and sent out to the real moving head

    Currently I am using a separate GameObject, let's call it "GimbalSource", which is located in the virtual moving heads center of rotation which points at the target using LookAt(). I then apply the euler x and y rotations of the GimbalSource object to the virtual moving head. I attached a picture and the code of a simplified setup (The GimbalSource object is invisible in the picture). So far this is working just fine.

    Code (CSharp):
    1.  public class MovingHead : MonoBehaviour
    2. {
    3.   public Transform tilt_axis_object;
    4.   public Transform pan_axis_object;
    5.   //Transform of GimbalSource which points at the actual target using LookAt()
    6.   public Transform GimbalSource;
    7.  
    8.   void Update()
    9.   {
    10.     tilt_axis_object.eulerAngles = new Vector3(
    11.       -GimbalSource.eulerAngles.x + 90,
    12.       tilt_axis_object.eulerAngles.y,
    13.       tilt_axis_object.eulerAngles.z
    14.     );
    15.  
    16.     pan_axis_object.eulerAngles = new Vector3(
    17.       pan_axis_object.eulerAngles.x,
    18.       GimbalSource.eulerAngles.y - 180,
    19.       pan_axis_object.eulerAngles.z
    20.     );
    21.   }
    22. };
    Now here's the actual problem:
    Obviously the position of the real moving head is not going to be perfectly aligned with the virtual coordinate system of Unity. So how do I get the same behaviour as described above, but with the moving head positioned and rotated (within reason) however I want? Also for readability it would probably be better if the actual target was referenced by the script directly instead of using an invisible GameObject.

    Could someone please share his wisdom of Quaternion wizardry or conjure up a few lines of magic code?

    Thanks a lot in advance!
     

    Attached Files:

  2. shimmr

    shimmr

    Joined:
    May 11, 2023
    Posts:
    3
    It really can't be that hard, right? I tried playing around with different up directions for LookAt() but it seems to go absolutely crazy, whenever the up direction is set to an angle that is not 90° to the world axis.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I certainly don't see that. I see a bunch of mucky-mucking about with eulerAngles and those are ALWAYS gonna be subject to gimbal lock at some step of the game.

    That's why we use Quaternions. :)

    All about Euler angles and rotations, by StarManta:

    https://starmanta.gitbooks.io/unitytipsredux/content/second-question.html

    Notes on clamping camera rotation and NOT using .eulerAngles because of gimbal lock:

    https://forum.unity.com/threads/implement-a-limit-in-camera-movement.1100038/#post-7082380
    https://forum.unity.com/threads/implement-a-limit-in-camera-movement.1100038/#post-7083097

    How to instantly see gimbal lock for yourself:

    https://forum.unity.com/threads/confusing-bug.1165789/#post-7471441
     
    shimmr likes this.
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    For a gimbal setup like that shown in the picture, you want to find 2 rotations:
    - yaw rotation of the carrier (around global Y axis)
    - pitch rotation of the suspended piece (around local X axis)
    - local roll of the suspended piece should be fixed

    So, basically pitch & yaw, which are the axis-aligned components of a single compound rotation.
    First find the compound rotation by using LookRotation, use Vector3.up as up to prevent rolling.
    The direction it should point at is found with
    Code (csharp):
    1. dir = (target - cam).normalized
    Then take this direction and project it onto the XZ plane
    Code (csharp):
    1. proj = flat(dir)
    where flat is
    Code (csharp):
    1. new Vector3(v.x, 0f, v.y).normalized
    Do another LookRotation with proj, this time you are absolutely sure the axis is normal to the XZ plane
    This is your yaw rotation.

    Apply the inverse of this rotation to the compound rotation from before (effectively a quaternion "subtraction")
    Code (csharp):
    1. pitch = inverse(yaw) * compound
    This is your pitch rotation.

    You can see more on this exact decomposition here on line 195

    This can produce zero vectors and undefined rotations if your target is at poles (up or down). To constrain the gimbal space you can use euler angles to produce spherical angles (pitch & yaw in radians/degrees) and then use that to limit the rotations. This and animated movement is also present in the example.
     
    Last edited: Jun 3, 2023
    shimmr likes this.
  5. shimmr

    shimmr

    Joined:
    May 11, 2023
    Posts:
    3
    Thanks for the help!

    I was playing around with your suggestion when I stumbled upon this post, which to my understanding is basically the same idea?

    https://discussions.unity.com/t/look-at-around-one-axis-in-local-space/227371

    So far it is working just fine. And yes, this does indeed break down if the target is at a pole! But that's actually okay for now as this should rarely occur in the real world setting. If it does happen to be a problem I'll look into using orionsyndrome's very sophisticated looking TurretBehaviour script.

    Just for reference, here's the working code for my example based on the mentioned post:
    Code (CSharp):
    1. Vector3 rotY = pan_axis_object.InverseTransformPoint(target.position);
    2. rotY.y = 0;
    3. pan_axis_object.LookAt(pan_axis_object.TransformPoint(rotY), pan_axis_object.up);
    4. Vector3 rotX = tilt_axis_object.InverseTransformPoint(target.position);
    5. rotX.x = 0;
    6. tilt_axis_object.LookAt(tilt_axis_object.TransformPoint(rotX), tilt_axis_object.parent.up);
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    @shimmr Yes it is identical. This decomposes on two planes, whereas I project onto a single plane and then differentiate between rotations. It's just semantics, but I also avoid having to do two inverse transforms and intermittent transformations back and forth, which would be my criticism of this code. It also lacks a certain finesse and clarity of what's going on, but that's just my personal taste.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I'm happy the code is working for OP, but I feel those lines of code are a bit on the long side, a bit overwrought.

    If you have more than one or two dots (.) in a single statement, you're just being mean to yourself.

    How to break down hairy lines of code:

    http://plbm.com/?p=248

    Break it up, practice social distancing in your code, one thing per line please.

    "Programming is hard enough without making it harder for ourselves." - angrypenguin on Unity3D forums

    "Combining a bunch of stuff into one line always feels satisfying, but it's always a PITA to debug." - StarManta on the Unity3D forums