Advertisement

Panning or centering location on a sphere/globe

Started by September 27, 2016 07:33 PM
3 comments, last by AntiTwister 7 years, 11 months ago

Hey all,

Was wondering if I code get some assistance implementing a snap-to function that snaps a users gaze point projection on a sphere to the user's/camera's projection transform onto the world. I want to maintain the world-up as to say I do not want the sphere rotation to twist/roll when rotating from one point to another / using a latitude and longitude coordinate system.

I have been using Unity so the code below is what I have been working with. I was storing the world rotation in a current latitude and longitude variable and subtracting it from the projection of the user projection ray onto the sphere. This way, the user can walk around the object and center/rotate the sphere to face the user/camera transform projection on the sphere. However, the result does not work as intended because as the user goes behind the sphere, the offsets should slowly be reversed. I suspect a Sin/Cos based on longitude but still can't make it work.

Intended behavior:

When user is in front of globe and requests "Center" on a location such as San Diego the globe moves San Diego from the San Diego location to the intersection of the (user camera position/ globe) ray on the globe.

When user is behind globe and requests "Center" on a location such as Tokyo the globe moves Tokyo from the Tokyo location to the intersection of the (user camera position/ globe) ray on the globe.

Code I have so far in C# Unity:


void Update()
{
        if(rotationInProgress)
        {
            // slowly move the currentLat / Lon towards our targetLat / Lon
            currentLat = Mathf.LerpAngle(currentLat, targetLat, Time.deltaTime * 5);
            currentLon = Mathf.LerpAngle(currentLon, targetLon, Time.deltaTime * 5);
            
            // build our rotation from the two angles
            transform.localRotation =
                Quaternion.AngleAxis(-currentLat, Vector3.right) *
                Quaternion.AngleAxis(currentLon, Vector3.up);

            if (Mathf.Abs(currentLat - targetLat) < 0.05f && Mathf.Abs(currentLon - targetLon) < 0.05f)
                rotationInProgress = false;
        }
}
void Center()
{
        // Do a raycast into the world based on the user's
        // head position and orientation.
        var headPosition = Camera.main.transform.position;
        var gazeDirection = Camera.main.transform.forward;
        var headGlobeDirection = transform.position - Camera.main.transform.position;

        RaycastHit hitInfo, posInfo;
        if (Physics.Raycast(headPosition, gazeDirection, out hitInfo) && Physics.Raycast(headPosition,headGlobeDirection,out posInfo))
        {

#region Determine offset location
            // convert the hit point into local coordinates
            Vector3 localPos = transform.InverseTransformPoint(posInfo.point);
            Vector3 longDir = localPos;
            // zero y to project the vector to the x-z-plane
            longDir.y = 0;

            //calculate the angle between our reference and the hitpoint
            float offsetLon = Vector3.Angle(-Vector3.forward, longDir);
            // if our point is on the western hemisphere negate the angle
            if (longDir.x < 0)
                offsetLon = -offsetLon;

            offsetLon -= currentLon;
            // calculate the latitude in degree
            float offsetLat = Mathf.Asin(localPos.normalized.y) * Mathf.Rad2Deg - currentLat;

#endregion

#region Determine Globe Pan-To Location
            // convert the hit point into local coordinates
            localPos = transform.InverseTransformPoint(hitInfo.point);
            longDir = localPos;
            // zero y to project the vector to the x-z-plane
            longDir.y = 0;

            //calculate the angle between our reference and the hitpoint
            targetLon = Vector3.Angle(-Vector3.forward, longDir);
            
            // if our point is on the western hemisphere negate the angle
            if (longDir.x < 0)
                targetLon = -targetLon;
            
            // calculate the latitude in degree
            targetLat = Mathf.Asin(localPos.normalized.y) * Mathf.Rad2Deg;

            targetLon -= offsetLon;
            targetLat -= offsetLat;
#endregion
            rotationInProgress = true;
        }
    }

Thanks in advance if this brings about a solution to my problem! If I am not clear I can provide explain more in a further post.

I am assuming that the globe should only rotate about the y-axis, if that assumption is incorrect let me know. Given that assumption, you are only dealing with a horizontal angle of rotation. In the local space of the globe, you can use target_angle = atan2(p.z, p.x) for a point on the surface in the local (unrotated) space of the globe to figure out the angle that corresponds to that point. Similarly, you can take view_angle = atan2(-viewray.z, -viewray.x) with your view direction to get the angle that is facing toward you when the globe is unrotated. The absolute world orientation needed will be the delta that rotates target_angle to match your view_angle, i.e. goal_angle = view_angle - target_angle. You will then want to smoothly interpolate from whatever angle the globe is currently using toward this goal_angle.
Advertisement
If you want to do something similar for the vertical tilt, you can store a second angle that is applied as a rotation after the horizontal rotation is applied, about the axis which is a cross product between the player's view and world up. The angle delta would be handled similarly to the horizontal, but instead of atan2 the easiest way to compute it would be asin(v.y / v.Length())

Thanks for the reply. I want to apply both longitude and latitude rotation.

What if the globe had a pre existing rotation applied? I want to animate from one rotation to the new center. Can the target angles calculated be added (or multiplied) to successive rotations?

I will try to implement what you said assuming the steps are:

1) Store current sphere rotation in temporary variable

2) Zero rotation on the globe

3) Raycast hit on the globe using view vector and camera position vector

4) Calculate longitudinal angles for both view and target angles (atan2(p.z, p.x))

5) Calculate goal angle

6) Calculate vertical angle delta using asin(v.y / v.Length())

7) Restore original rotation to globe

8) Apply longitudinal delta to globe

9) Apply latitude delta to globe on the view/up cross product axis

but I think I will still have the issue of vertical rotations on the back side of the globe not being the same as the front side. My solution right now results in latitude requiring to be inverted when camera is looking on the backside. For example starting with a view rotation looking at the US and recentering after moving the camera (not the look direction) towards the eastern hemisphere.

For smooth interpolation of orientations you will want to store them in quaternions. Typically there are functions that will allow you to compute a quaternion from an axis/angle representation, and once you have it in quaternion form you can concatenate rotations by multiplying, and rotate the opposite direction by taking the inverse.

You will want to store two quaternions. The first is the current orientation of the globe, and the second is the goal orientation of the globe. These are both rotations that would take you from the original, unrotated state of the globe to a world space orientation. The goal orientation will be computed by multiplying the rotations needed to move from an unrotated state until the target is aligned horizontally, followed by the rotation to align it vertically.

Once you have these two orientations, you can interpolate between them using slerp (generally provided with any quaternion implementation). If it isn't available, you can compute the delta rotation as the inverse of where you started concatenated with your goal; i.e. the rotation that undoes your current orientation and then rotates to your goal orientation. Convert this to angle-axis, scale down the angle, convert back to quaternion and then multiply in to your current orientation to move partway to the goal.

This topic is closed to new replies.

Advertisement