创建Google地球,例如球体导航

时间:2017-11-04 20:44:30

标签: c# math unity3d 3d geometry

这个问题是关于Unity3D的。 我想创建类似于Google Earth的导航,您可以在其中单击并拖动球体,然后让相机进行相应的轨道运动。重要的是,在拖动时抓取的点始终位于鼠标位置下方。如果我缩放到球体附近,导航也应该有效。我不想旋转球体本身。就像Google Earth一样。

如果我开始拖动,我的尝试是将鼠标位置投射到球体上。在下一帧我做同样的事情并计算起始拖动和结束拖动位置之间的角度。

enter image description here

private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
    // calc the rotation of the drag
    float angle = Vector3.Angle(dragStart, dragEnd);
    // rotate the camera around the sphere 
    Camera.main.transform.RotateAround(sphere), Vector3.up, angle);
}

我想过使用Unitys RotateAround 方法以计算出的角度旋转相机。不幸的是我没有旋转向量(在示例中使用Vector3.up显然是错误的)。 有人知道如何计算这个向量来将它应用于该方法吗?我是否正确地实施Google地球导航?

谢谢!

更新 我非常接近一个新的解决方案。我将拖动矢量投影到向下和向右平面以获得角度。然后我将相机向上旋转并向左旋转。这很有效,直到我到达球体的两极。如果我到达极点,相机会自动旋转。

private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
    Vector3 plane = Vector3.down;
    var a = Vector3.ProjectOnPlane(dragStart, plane);
    var b = Vector3.ProjectOnPlane(dragEnd, plane);
    float up = Vector3.SignedAngle(a, b, plane);

    plane = Vector3.right;
    a = Vector3.ProjectOnPlane(dragStart, plane);
    b = Vector3.ProjectOnPlane(dragEnd, plane);
    float left = Vector3.SignedAngle(a, b, plane);

    Camera.main.transform.RotateAround(_sphere, Vector3.up, up);
    Camera.main.transform.RotateAround(_sphere, Vector3.left, left);
}

2 个答案:

答案 0 :(得分:1)

基于鼠标拖动的基本旋转,基于您拥有的内容:

Transform camTransform = Camera.main.transform;
if (Input.GetMouseButton(0))
{
    camTransform.RotateAround(currentLookTargetTransform.position, -camTransform.right * Input.GetAxis("Mouse Y") + camTransform.up * Input.GetAxis("Mouse X"), 120 * Time.deltaTime);
}

您可以将相对方向乘以鼠标更改值以获得轴。然后你可以取代你的夹点;但关键是要相对旋转它。

答案 1 :(得分:1)

事实证明这比我预期的要容易。我考虑计算旋转轴并得出结论,它必须是开始和结束向量的交叉乘积。看看解决方案。 RotateCamera 方法是 math magic 发生的地方:)

public class GoogleEarthControls : MonoBehaviour
{
    private const int SpehreRadius = 1;
    private Vector3? _mouseStartPos;
    private Vector3? _currentMousePos;

    void Start () {
        // init the camera to look at this object
        Vector3 cameraPos = new Vector3(
            transform.position.x, 
            transform.position.y, 
            transform.position.z - 2);

        Camera.main.transform.position = cameraPos;
        Camera.main.transform.LookAt(transform.position);
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0)) _mouseStartPos = GetMouseHit();
        if (_mouseStartPos != null) HandleDrag();
        if (Input.GetMouseButtonUp(0)) HandleDrop();
    }

    private void HandleDrag()
    {
        _currentMousePos = GetMouseHit();
        RotateCamera((Vector3) _mouseStartPos, (Vector3)_currentMousePos);
    }

    private void HandleDrop()
    {
        _mouseStartPos = null;
        _currentMousePos = null;
    }

    private void RotateCamera(Vector3 dragStartPosition, Vector3 dragEndPosition)
    {
        // in case the spehre model is not a perfect sphere..
        dragEndPosition = dragEndPosition.normalized * SpehreRadius;
        dragStartPosition = dragStartPosition.normalized * SpehreRadius;
        // calc a vertical vector to rotate around..
        var cross = Vector3.Cross(dragEndPosition, dragStartPosition);
        // calc the angle for the rotation..
        var angle = Vector3.SignedAngle(dragEndPosition, dragStartPosition, cross);
        // roatate around the vector..
        Camera.main.transform.RotateAround(transform.position, cross, angle);
    }

    /**
     * Projects the mouse position to the sphere and returns the intersection point. 
     */
    private static Vector3? GetMouseHit()
    {
        // make sure there is a shepre mesh with a colider centered at this game object
        // with a radius of SpehreRadius
        RaycastHit hit;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
        {
            return hit.point;
        }
        return null;
    }
}