title: Zoom camera's aim is off. Quaternions completely baffling me
body: <p><strong>DESCRIPTION</strong></p>
  <p>I am using the MouseLook script below that I got from Unity forums to move my camera around. I am attempting to create a simple zoom effect by increasing the Field of View then having the transform look at the point I want. The point I am aiming at is the exact one I want as tested using CreatePrimitive() on top of it.</p>
  <p><strong>PROBLEM</strong></p>
  <p>When holding the right mouse button to activate the zoom, the aim is always off by a rather significant amount (and it's not because the position of the objects changes in relation to the mouse cursor after the zoom). Here is a video: <a href="https://streamable.com/8aiil0" rel="nofollow noreferrer">https://streamable.com/8aiil0</a></p>
  <p><strong>WHAT I TRIED</strong></p>
  <p>I managed to trace the problem to the line I marked with <code>// ************** PROBLEMATIC LINE *************** //</code> in the script above. If I comment it out and apply only the rotation below it, the zoom doesn't throw the aim completely off but the camera can no longer move up and down and the horizontal rotation becomes extremely sensitive to mouse movements.</p>
  <p>I know that the variable <code>targetDirection</code>, which is set to the camera's initial <code>transform.localEulerAngles</code> value on start, is involved somehow. I tried updating it to the camera's <code>localEulerAngles</code> after mouse movement to see if it would work but it didn't.</p>
  <p>I tried a bunch of other things that didn't work. Unfortunately I know virtually nothing about Quaternions so this is all I could do.</p>
  <p><strong>MINIMUM REPRODUCIBLE EXAMPLE</strong></p>
  <p>I sincerely apologize for not being able to figure this out on my own. I know you guys have far better things to do than debug other people's projects but in case an angel among you is feeling extra bored, I have attached a minimum reproducible example below as a .unitypackage:</p>
  <p><a href="https://easyupload.io/q4jwfl" rel="nofollow noreferrer">https://easyupload.io/q4jwfl</a></p>
  <p><strong>CAMERA MOVEMENT SCRIPT</strong></p>
  <pre><code>using UnityEngine;
  
  [AddComponentMenu(&quot;Camera/Mouse Look&quot;)]
  public class MouseLook : MonoBehaviour
  {
      Vector2 _mouseAbsolute;
      Vector2 _smoothMouse;
  
      public Vector2 clampInDegrees = new Vector2(360, 180);
      public bool lockCursor;
      public Vector2 sensitivity = new Vector2(2, 2);
      public Vector2 smoothing = new Vector2(3, 3);
      public Vector2 targetDirection;
  
      void Start()
      {
          // Set target direction to the camera's initial orientation.
          if (targetDirection == null || targetDirection == Vector2.zero)
          {
              targetDirection = transform.localRotation.eulerAngles;
          }
      }
  
      void Update()
      {
          // Ensure the cursor is always locked when set
          if (lockCursor)
              Cursor.lockState = CursorLockMode.Locked;
  
          // Allow the script to clamp based on a desired target value.
          var targetOrientation = Quaternion.Euler(targetDirection);
  
          // Get raw mouse input for a cleaner reading on more sensitive mice.
          var mouseDelta = new Vector2(Input.GetAxisRaw(&quot;Mouse X&quot;), Input.GetAxisRaw(&quot;Mouse Y&quot;));
  
          // Scale input against the sensitivity setting and multiply that against the smoothing value.
          mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y));
  
          // Interpolate mouse movement over time to apply smoothing delta.
          _smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x);
          _smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y);
  
          // Find the absolute mouse movement value from point zero.
          _mouseAbsolute += _smoothMouse;
  
          // Clamp and apply the local x value first, so as not to be affected by world transforms.
          if (clampInDegrees.x &lt; 360)
              _mouseAbsolute.x = Mathf.Clamp(_mouseAbsolute.x, -clampInDegrees.x * 0.5f, clampInDegrees.x * 0.5f);
  
          // Then clamp and apply the global y value.
          if (clampInDegrees.y &lt; 360)
              _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f);
  
          // ************** PROBLEMATIC LINE *************** //
          transform.localRotation = Quaternion.AngleAxis(-_mouseAbsolute.y, targetOrientation * Vector3.right) * targetOrientation;
  
          var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, transform.InverseTransformDirection(Vector3.up));
  
          transform.localRotation *= yRotation;
      }
  }
  </code></pre>
  <p><strong>ZOOM SCRIPT</strong></p>
  <pre><code>void Update()
      {
          // if the right mouse button is being held down, zoom in
          if (Input.GetMouseButton(1))
          {
              // get the location of the point the mouse is over
              int layerMask = 1 &lt;&lt; CombatManager.GROUND_LAYER; // -&gt; public const int GROUND_LAYER = 8;
              Ray mouseClick = Camera.main.ScreenPointToRay(Input.mousePosition);
              RaycastHit hitInfo;
  
              Physics.Raycast(mouseClick, out hitInfo, Mathf.Infinity, layerMask);
  
              // if the zoom is not yet active
              if (false == zoomIsActive)
              {
                  mainCamera.fieldOfView = 10;
                  mainCamera.transform.LookAt(hitInfo.point);
  
                  // DEBUG: make sure the point you are clicking on is the point being used to look at
                  GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                  cube.transform.position = hitInfo.point;
              }
  
              zoomIsActive = true;
          }
  
          // turn off zoom
          else
          {
              mainCamera.fieldOfView = startingFieldOfView;
              zoomIsActive = false;
          }
      }
  </code></pre>