Search Unity

How To Get Pitch and Yaw Of Goal Position For Camera Orbit [SOLVED]

Discussion in 'Scripting' started by keenanwoodall, Jul 22, 2016.

  1. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    I've got a simple camera that orbits around a target by using the RotateAround method.

    I want to prevent it from going too high or low. I've figured out what (on paper) should work. Camera Limit Explanation.png

    After implementing this, the camera will still do off things. It will move itself so that the target is no longer centered and the camera isn't looking at it, or it'll get stuck.



    Here's my code. The "ClampHeight" method is where I'm performing the calculations that I summarized in my drawing.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using System.Collections;
    5. [RequireComponent (typeof (Rigidbody))]
    6. public class MouseOrbit : MonoBehaviour
    7. {
    8.     #region Public Properties
    9.  
    10.     public Transform target;
    11.  
    12.     public float distance = 5.0f;
    13.     public float xSpeed = 120.0f;
    14.     public float ySpeed = 120.0f;
    15.     public float deaccelerationTime = 0.1f;
    16.     public float zoomSpeed = 2.5f;
    17.  
    18.     [Range (0.5f, 5f)]
    19.     public float distanceMin = .5f;
    20.     [Range (5f, 20f)]
    21.     public float distanceMax = 15f;
    22.  
    23.     #endregion
    24.  
    25.  
    26.  
    27.     #region Hidden Properties
    28.  
    29.     private Rigidbody rb;
    30.  
    31.     private float xVelocity = 0.0f;
    32.     private float yVelocity = 0.0f;
    33.  
    34.     private float xRefVelocity, yRefVelocity;
    35.  
    36.     private bool autoOrbiting = false;
    37.  
    38.     #endregion
    39.  
    40.  
    41.  
    42.     #region Main Methods
    43.  
    44.     void Start ()
    45.     {
    46.         rb = GetComponent<Rigidbody> ();
    47.  
    48.         if (rb != null)
    49.         {
    50.             rb.freezeRotation = true;
    51.             rb.isKinematic = true;
    52.         }
    53.     }
    54.  
    55.     void LateUpdate ()
    56.     {
    57.         if (target)
    58.         {
    59.             if (Input.GetMouseButton (0) && !EventSystem.current.IsPointerOverGameObject () && !autoOrbiting)
    60.             {
    61.                 xVelocity = (Input.GetAxis ("Mouse X") * xSpeed * distance * 0.02f);
    62.                 yVelocity = -(Input.GetAxis ("Mouse Y") * ySpeed * 0.02f);
    63.  
    64.                 if (xVelocity == 0f)
    65.                     xRefVelocity = 0f;
    66.                 if (yVelocity == 0f)
    67.                     xRefVelocity = 0f;
    68.             }
    69.  
    70.             xVelocity = Mathf.SmoothDamp (xVelocity, 0f, ref xRefVelocity, deaccelerationTime);
    71.             yVelocity = Mathf.SmoothDamp (yVelocity, 0f, ref yRefVelocity, deaccelerationTime);
    72.  
    73.             Quaternion rotation = Quaternion.Euler (yVelocity, xVelocity, 0);
    74.  
    75.             distance = Mathf.Clamp (distance - Input.GetAxis ("Mouse ScrollWheel") * zoomSpeed, distanceMin, distanceMax);
    76.  
    77.             transform.position = ((transform.position - target.position).normalized * distance) + target.position;
    78.  
    79.             Vector3 preMoveDirection = (target.position - transform.position).normalized;
    80.  
    81.             transform.RotateAround (target.position, transform.right, yVelocity);
    82.             transform.RotateAround (target.position, transform.up, xVelocity);
    83.  
    84.             Vector3 postMoveDirection = (target.position - transform.position).normalized;
    85.  
    86.             ClampHeight (preMoveDirection, postMoveDirection, 10f);
    87.  
    88.             ClampZ ();
    89.         }
    90.     }
    91.  
    92.     #endregion
    93.  
    94.  
    95.  
    96.     #region Utility Methods
    97.  
    98.     private float ClampAngle (float angle, float min, float max)
    99.     {
    100.         if (angle < -360f)
    101.             angle += 360f;
    102.         if (angle > 360f)
    103.             angle -= 360f;
    104.  
    105.         return Mathf.Clamp (angle, min, max);
    106.     }
    107.  
    108.     private void ClampZ ()
    109.     {
    110.         Vector3 angles = transform.eulerAngles;
    111.         angles.z = 0;
    112.         transform.eulerAngles = angles;
    113.     }
    114.  
    115.     private void ClampHeight (Vector3 preDirection, Vector3 postDirection, float minAngle)
    116.     {
    117.         int above = (postDirection.y > 0f) ? 1 : -1;
    118.         Vector3 nearestPoleDirection = Vector3.up * above;
    119.  
    120.         float goalToUpAngle = Vector3.Angle (nearestPoleDirection, postDirection);
    121.  
    122.         Vector3 turnAxis = Vector3.Cross (preDirection, postDirection);
    123.  
    124.         if (goalToUpAngle < minAngle)
    125.         {
    126.             float angleDifference = minAngle - goalToUpAngle;
    127.             transform.RotateAround (target.position, turnAxis, angleDifference * -above);
    128.  
    129.             yVelocity = 0f;
    130.         }
    131.     }
    132.  
    133.  
    134.     private IEnumerator turnCoroutine;
    135.     public void TurnTo (string locationName)
    136.     {
    137.         Location findLocation = LocationManager.Instance.FindLocationByName (locationName);
    138.  
    139.         if(turnCoroutine != null)
    140.             StopCoroutine (turnCoroutine);
    141.  
    142.         turnCoroutine = Turn (LatLongOnSphere.CalculateNormalizedPosition (findLocation), 0.5f);
    143.         StartCoroutine (turnCoroutine);
    144.     }
    145.     public void TurnTo (Location location)
    146.     {
    147.         if (turnCoroutine != null)
    148.             StopCoroutine (turnCoroutine);
    149.  
    150.         turnCoroutine = Turn (LatLongOnSphere.CalculateNormalizedPosition (location), 0.5f);
    151.         StartCoroutine (turnCoroutine);
    152.     }
    153.  
    154.     #endregion
    155.  
    156.  
    157.  
    158.     #region Coroutines
    159.  
    160.     private IEnumerator Turn (Vector3 goal, float time)
    161.     {
    162.         xVelocity = 0;
    163.         yVelocity = 0;
    164.  
    165.         autoOrbiting = true;
    166.  
    167.         float timePassed = 0f,
    168.             startX = xVelocity,
    169.             startY = yVelocity;
    170.  
    171.         Vector3 startDirection = (target.position - transform.position).normalized;
    172.         Vector3 goalDirection = (target.position - goal).normalized;
    173.  
    174.         Vector3 turnAxis = Vector3.Cross (startDirection, goalDirection);
    175.         float angle = Vector3.Angle (startDirection, goalDirection);
    176.  
    177.         Vector3 startPosition = transform.position.normalized;
    178.         Vector3 currentPosition = new Vector3 ();
    179.  
    180.         while (timePassed / time < 1f)
    181.         {
    182.             timePassed += Time.deltaTime;
    183.             timePassed = Mathf.Clamp (timePassed, 0, time);
    184.  
    185.             float t = timePassed / time;
    186.             t = t * t * t * (t * (6f * t - 15f) + 10f);
    187.  
    188.             currentPosition = Vector3.Lerp (startPosition, goal, t);
    189.  
    190.             Vector3 currentDirection = (target.position - transform.position).normalized;
    191.             Vector3 directionDelta = (target.position - currentPosition).normalized;
    192.  
    193.             float nextAngle = Vector3.Angle (currentDirection, directionDelta);
    194.             transform.RotateAround (target.position, turnAxis, nextAngle);
    195.  
    196.             yield return 0;
    197.         }
    198.  
    199.         yield return 0;
    200.  
    201.         autoOrbiting = false;
    202.     }
    203.  
    204.     #endregion
    205. }
    206.  
    207.  
     
    Last edited: Jul 22, 2016
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If you're trying to clamp the "height" and height is defined as the distance between the camera and the center of the globe then don't you want to move the camera back and forth along it's forward axis and not RotateAround again? Unless I'm misunderstanding what you're trying to do. I'd think you'd also want to LookAt the center of the globe after you rotate.
     
  3. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    Sorry, "high" was a bad word. I don't want the camera to get too close to the north or south pole because if you get to the very top, and are looking straight down or straight up, the camera freaks out because it doesn't know which way to turn. I want to prevent the cameras forward vector from coming too close to north/up or south/down vector.

    EDIT: And I also want to clamp its height for functional aesthetics, regardless of the necessity to clamp it to prevent a broken camera.
     
  4. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Have 3 control floats: pitch, yaw and distance.
    The pitch and yaw floats have your input added to them for x and y inputs. distance is how far away you'd like the camera to be. There should also be a targetPosition (your planet).

    Then the camera's direction + rotation is:

    Code (CSharp):
    1. Quaternion camRotation = Quaternion.Euler(pitch, yaw, 0);
    2. Vector3 camDirection = (camRotation * Vector3.forward).normalized;
    And the position is:

    Code (CSharp):
    1. Vector3 camPosition = targetPosition + camDirection * -distance;
    You then assign the rotation and position to the camera as normal. If you want to clamp before the poles, it's as trivial as pitch = Mathf.Clamp(pitch, -70, 70); for example.

    So the camera is only fed a rotation and position. You never read from the camera. Good code is simple but requires a different way of thinking.
     
  5. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    Thanks, but the reason I couldn't do that is because I need to be able to move the camera to a given position via an IEnumerator (see the Turn coroutine) and not have the camera moved back to where the yaw and pitch says it should go, when the coroutine finishes.

    Whatever changes I make to the position and rotation of the camera need to be additive so that I can change movement modes (auto orbit to a position) without the transform being reset to whatever is calculated based on, in this case pitch and yaw.

    Why I Can't Set The Transform.png

    So I need a way to modify the pitch and yaw values directly to orbit towards a point, or I have to have two movement solutions that cannot override eachother
     
  6. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    The code I posted is deterministic and thus compatible with co-routines and everything imaginable. You just need to add to pitch and yaw within your co-routine. As I said, you need to think about the way you're thinking about this. Otherwise you'll keep coding yourself into places you can't get out of.
     
  7. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    I got that, but I don't know how to calculate the amount of pitch/yaw to add in the coroutine. I don't know how much pitch/yaw to add based on the position the camera needs to move to. Is there a method that I should know of? Also, good advice on not overcomplicating the code, that's an area which I have yet to master! o_O
     
  8. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    So I guess my next question is, how can I calculate the pitch and yaw of the goal?

    I know I can get its normalized position relative to the target. Then I can get its direction, but I don't know how to use that info. How can I turn a direction into pitch and yaw?
     
  9. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    598
    Ok, I figured it out. I was stuck on getting a pitch and yaw value from a goal position. I knew how to get from pitch/yaw to a position, but not the other way around. Here's the code:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using System.Collections;
    5.  
    6. public class CameraOrbit : MonoSingleton <CameraOrbit>
    7. {
    8.     #region Constant Properties
    9.  
    10.     private const float
    11.         MIN_PITCH =            -80f,
    12.         MAX_PITCH =            80,
    13.         PITCH_MULT =        1.25f,
    14.         MIN_DIST =            3f,
    15.         MAX_DIST =            8f,
    16.         AUTO_TURN_TIME =    1f;
    17.  
    18.     private const Interpolation.InterpType AUTO_TURN_TYPE = Interpolation.InterpType.SmootherStep;
    19.  
    20.     #endregion
    21.  
    22.  
    23.  
    24.     #region Public Properties
    25.  
    26.     [Range (MIN_PITCH, MAX_PITCH), SerializeField]
    27.     private float pitch = 0f;
    28.     /// <summary>
    29.     /// How far the camera has spun above and below the target on its x axis.
    30.     /// </summary>
    31.     public float Pitch
    32.     {
    33.         get { return pitch; }
    34.         set { pitch = Mathf.Clamp (value, MIN_PITCH, MAX_PITCH); }
    35.     }
    36.  
    37.     [Range (-360f, 360f), SerializeField]
    38.     private float yaw = 0f;
    39.     /// <summary>
    40.     /// How far the camera has spun around the target on its y axis.
    41.     /// </summary>
    42.     public float Yaw
    43.     {
    44.         get { return yaw; }
    45.         set { yaw = LoopAngle (value); }
    46.     }
    47.  
    48.     /// <summary>
    49.     /// The speed at which the camera orbits around via user input.
    50.     /// </summary>
    51.     [Range (0f, 10f)]
    52.     public float orbitSpeed = 2f;
    53.  
    54.     [Range(0f, 2f)]
    55.     public float zoomSpeed = 1.5f;
    56.  
    57.     /// <summary>
    58.     /// This affects the speed that you can orbit depending on where you are in relation to the min and max zoom distance.
    59.     /// </summary>
    60.     public AnimationCurve distanceOrbitSpeedMultiplier = new AnimationCurve ();
    61.     /// <summary>
    62.     /// This affects the speed that you can zoom depending on where you are in relation to the min and max zoom distance.
    63.     /// </summary>
    64.     public AnimationCurve distanceSpeedMultiplier = new AnimationCurve ();
    65.  
    66.     [Range (MIN_DIST, MAX_DIST), SerializeField]
    67.     private float distance = 5f;
    68.     /// <summary>
    69.     /// The amount of space between the camera and its target.
    70.     /// </summary>
    71.     public float Distance
    72.     {
    73.         get
    74.         {
    75.             return distance;
    76.         }
    77.         set
    78.         {
    79.             distance = Mathf.Clamp (value, MIN_DIST, MAX_DIST);
    80.         }
    81.     }
    82.  
    83.     /// <summary>
    84.     /// The time it takes for the yaw and pitch velocity to go back to zero.
    85.     /// </summary>
    86.     public float orbitVelocityDampening = 0.2f;
    87.  
    88.     /// <summary>
    89.     /// The time it takes for the "distanceVelocity" to go back to zero.
    90.     /// </summary>
    91.     public float distanceVelocityDampening = 0.2f;
    92.  
    93.     /// <summary>
    94.     /// Can the user manually change the pitch and yaw with their mouse?
    95.     /// </summary>
    96.     public bool enableUserControl { get; set; }
    97.  
    98.     /// <summary>
    99.     /// This is the object that the camera will orbit around.
    100.     /// </summary>
    101.     public Transform target;
    102.  
    103.     #endregion
    104.  
    105.  
    106.  
    107.     #region Private Properties
    108.  
    109.     /// <summary>
    110.     /// Is the camera currently being controlled by the "Turn" coroutine?
    111.     /// </summary>
    112.     public bool autoTurning { get; private set; }
    113.  
    114.     /// <summary>
    115.     /// The velocity of the distance.
    116.     /// </summary>
    117.     public float distanceVelocity { get; private set; }
    118.     private float distanceVeloctyRef;
    119.  
    120.     /// <summary>
    121.     /// The velocity of the pitch.
    122.     /// </summary>
    123.     public float pitchVelocity { get; private set; }
    124.     private float pitchVelocityRef;
    125.     /// <summary>
    126.     /// The velocity of the yaw.
    127.     /// </summary>
    128.     public float yawVelocity { get; private set; }
    129.     private float yawVelocityRef;
    130.  
    131.     #endregion
    132.  
    133.  
    134.  
    135.     #region Main Methods
    136.  
    137.     void Start ()
    138.     {
    139.         // Auto-turning shouldn't be tru on start and we want the user to have control.
    140.         autoTurning = false;
    141.         enableUserControl = true;
    142.     }
    143.  
    144.     void Update ()
    145.     {
    146.         // Do we want the user to be able to control the pitch and yaw manually?
    147.         if (enableUserControl)
    148.         {
    149.             // "Distance" will be modified by the users scroll wheel.
    150.             // The speed at which the distance changes is based on the "distanceSpeedMultiplier" curve.
    151.             distanceVelocity -= Input.GetAxis ("Mouse ScrollWheel") * distanceSpeedMultiplier.Evaluate (Mathf.InverseLerp (MIN_DIST, MAX_DIST, Distance)) * zoomSpeed;
    152.             Distance += distanceVelocity;
    153.  
    154.             // If the mouse is down but we aren't auto-turning, the user can control the pitch and yaw.
    155.             if (Input.GetMouseButton (0) && !autoTurning)
    156.             {
    157.                 // We'll dampen the speed that the user can orbit based on the "distanceOrbitSpeedMultiplier" curve.
    158.                 float distanceSpeedDamper = distanceOrbitSpeedMultiplier.Evaluate (Mathf.InverseLerp (MIN_DIST, MAX_DIST, Distance));
    159.  
    160.                 // We'll add user input to the pitch and yaw velocity.
    161.                 yawVelocity += Input.GetAxis ("Mouse X") * orbitSpeed * distanceSpeedDamper;
    162.                 pitchVelocity -= Input.GetAxis ("Mouse Y") * orbitSpeed * distanceSpeedDamper * PITCH_MULT;
    163.  
    164.             }
    165.         }
    166.         // Add the velocity to the pitch and yaw values.
    167.         Yaw += yawVelocity;
    168.         Yaw = LoopAngle (Yaw);
    169.         Pitch += pitchVelocity;
    170.  
    171.         // We want to be constantly lowering the velocity so that the camera doesn't continue moving or zooming forever.
    172.         distanceVelocity = Mathf.SmoothDamp (distanceVelocity, 0f, ref distanceVeloctyRef, distanceVelocityDampening);
    173.         yawVelocity = Mathf.SmoothDamp (yawVelocity, 0f, ref yawVelocityRef, orbitVelocityDampening);
    174.         pitchVelocity = Mathf.SmoothDamp (pitchVelocity, 0f, ref pitchVelocityRef, orbitVelocityDampening);
    175.  
    176.         // The cameras position and rotation need to reflect any changes to the pitch and yaw.
    177.         transform.rotation = CalcRotation (Pitch, Yaw);
    178.         transform.position = CalcPosition (target.position, CalcDirection (transform.rotation), Distance);
    179.     }
    180.  
    181.     #endregion
    182.  
    183.  
    184.  
    185.     #region Utility Methods
    186.  
    187.     /// <summary>
    188.     /// Calculates the proper rotation of an object that has the supplied pitch and yaw.
    189.     /// </summary>
    190.     /// <param name="pitch"></param>
    191.     /// <param name="yaw"></param>
    192.     /// <returns></returns>
    193.     private Quaternion CalcRotation (float pitch, float yaw)
    194.     {
    195.         return Quaternion.Euler (pitch, yaw, 0);
    196.     }
    197.  
    198.     /// <summary>
    199.     /// Calculates the direction of the supplied rotation.
    200.     /// Use "CalcRotation" for the correct Quaternion to pass to this method.
    201.     /// </summary>
    202.     /// <param name="rotation"></param>
    203.     /// <returns></returns>
    204.     private Vector3 CalcDirection (Quaternion rotation)
    205.     {
    206.         return (rotation * Vector3.forward).normalized;
    207.     }
    208.  
    209.     /// <summary>
    210.     /// Calculates the proper position based on the supplied targetPosition, direction and distance.
    211.     /// Use "CalcDirection" for the correct direction to pass to this method.
    212.     /// </summary>
    213.     /// <param name="targetPosition"></param>
    214.     /// <param name="direction"></param>
    215.     /// <param name="distance"></param>
    216.     /// <returns></returns>
    217.     private Vector3 CalcPosition (Vector3 targetPosition, Vector3 direction, float distance)
    218.     {
    219.         return targetPosition + direction * -distance;
    220.     }
    221.  
    222.     /// <summary>
    223.     /// Keeps an angle between -360 and 360 degrees.
    224.     /// </summary>
    225.     /// <param name="angle"></param>
    226.     /// <returns></returns>
    227.     private float LoopAngle (float angle)
    228.     {
    229.         while (angle < -360f)
    230.             angle += 360f;
    231.         while (angle > 360f)
    232.             angle -= 360f;
    233.  
    234.         return angle;
    235.     }
    236.  
    237.     /// <summary>
    238.     /// Sets the turn velocity of the pitch and yaw to zero, as well as the damping velocity.
    239.     /// </summary>
    240.     private void StopOrbiting ()
    241.     {
    242.         yawVelocity = 0f;
    243.         yawVelocityRef = 0f;
    244.         pitchVelocity = 0f;
    245.         pitchVelocityRef = 0f;
    246.     }
    247.  
    248.     #endregion
    249.  
    250.  
    251.  
    252.     #region Coroutines
    253.  
    254.     private IEnumerator turnCoroutine;
    255.     /// <summary>
    256.     /// Finds a location based on the supplied locationName and starts the coroutine to move the camera to look at it from behind.
    257.     /// </summary>
    258.     /// <param name="locationName">The name of the location to turn towards.</param>
    259.     public void TurnTo (string locationName)
    260.     {
    261.         // We'll find the location based on the supplied name, and then find that locations corresponding marker.
    262.         Location location = LocationManager.Instance.FindLocationByName (locationName);
    263.         Marker marker = LocationManager.Instance.FindMarkerByLocation (location);
    264.  
    265.         // Then we'll get the markers pitch and yaw.
    266.         Vector2 pitchAndYaw = marker.CalcPitchandYaw ();
    267.  
    268.         // Before we start auto-turning we need to stop and current auto-turning coroutines, if they exist.
    269.         if (turnCoroutine != null)
    270.             StopCoroutine (turnCoroutine);
    271.  
    272.         // Now we can start auto-turning.
    273.         turnCoroutine = Turn (pitchAndYaw.x, pitchAndYaw.y);
    274.         StartCoroutine (turnCoroutine);
    275.     }
    276.  
    277.     /// <summary>
    278.     ///
    279.     /// </summary>
    280.     /// <param name="pitchGoal">The pitch that the camera will moved towards.</param>
    281.     /// <param name="yawGoal">The yaw that the camera will move towards.</param>
    282.     /// <returns></returns>
    283.     private IEnumerator Turn (float pitchGoal, float yawGoal)
    284.     {
    285.         // Before we do anything, we'll want to remove any velocity that might affect our auto-orbiting action.
    286.         StopOrbiting ();
    287.  
    288.         float timePassed = 0f;
    289.  
    290.         // The pitch and yaw that we will lerp from.
    291.         float
    292.             startPitch = Pitch,
    293.             startYaw = Yaw;
    294.  
    295.         while (timePassed < AUTO_TURN_TIME)
    296.         {
    297.             // While we are auto-turning we want to make sure that "autoTurning" is always true.
    298.             autoTurning = true;
    299.  
    300.             timePassed += Time.deltaTime;
    301.  
    302.             // "t" will interpolate between 0 and 1 over "AUTO_TURN_TIME" seconds.
    303.             float t = timePassed / AUTO_TURN_TIME;
    304.             t = Interpolation.Interpolate(AUTO_TURN_TYPE, t);
    305.  
    306.             Pitch = Mathf.LerpAngle (startPitch, pitchGoal, t);
    307.             Yaw = Mathf.LerpAngle (startYaw, yawGoal, t);
    308.  
    309.             yield return null;
    310.         }
    311.  
    312.         // Once we are done turning, we can set autoTurning back to false.
    313.         // Now the user can resume manual control of the yaw and pitch.
    314.         autoTurning = false;
    315.  
    316.         yield return null;
    317.     }
    318.  
    319.     #endregion
    320. }
    321.  
     
    Last edited: Jul 26, 2016