Formeln

Friday, March 22, 2013

Orbit and Pan Camera

In the last tutorial I explained how to implement an Orbit Camera, with which you can circle around a given point. In this tutorial I will explain how to add the capability to pan. Panning means translating the camera parallel to the X-Z plane.

Source Code of the OrbitPanCamera


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;

namespace Apparat
{
    public class OrbitPanCamera
    {
        #region Singleton Pattern
        private static OrbitPanCamera instance = null;
        public static OrbitPanCamera Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new OrbitPanCamera();
                }
                return instance;
            }
        }
        #endregion

        #region Constructor
        private OrbitPanCamera()
        {
            eye = new Vector3(4, 2, 0);
            target = new Vector3(0, 0, 0);
            up = new Vector3(0, 1, 0);

            view = Matrix.LookAtLH(eye, target, up);
            perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.0f, 1.0f);
        }
        #endregion

        Vector3 eye;
        Vector3 target;
        Vector3 up;

        Matrix view = Matrix.Identity;
        Matrix perspective = Matrix.Identity;
        Matrix viewPerspective = Matrix.Identity;

        public Matrix View
        {
            get { return view; }
        }

        public void setPerspective(float fov, float aspect, float znear, float zfar)
        {
            perspective = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar);
        }

        public void setView(Vector3 eye, Vector3 target, Vector3 up)
        {
            view = Matrix.LookAtLH(eye, target, up);
        }

        public Matrix Perspective
        {
            get { return perspective; }
        }

        public Matrix ViewPerspective
        {
            get { return view * perspective; }
        }

        float rotY = 0;

        public void rotateY(int value)
        {
            rotY = (value / 100.0f);
            Vector3 eyeLocal = eye - target;

            Matrix rotMat = Matrix.RotationY(rotY);
            eyeLocal = Vector3.TransformCoordinate(eyeLocal, rotMat);
            eye = eyeLocal + target;

            setView(eye, target, up);
        }
        float rotOrtho = 0;

        public void rotateOrtho(int value)
        {
            Vector3 viewDir = target - eye;
            Vector3 orhto = Vector3.Cross(viewDir, up);
            
            rotOrtho = (value / 100.0f);
            Matrix rotOrthoMat = Matrix.RotationAxis(orhto, rotOrtho);

            Vector3 eyeLocal = eye - target;
            eyeLocal = Vector3.TransformCoordinate(eyeLocal, rotOrthoMat);
            Vector3 newEye = eyeLocal + target;
            Vector3 newViewDir = target - newEye;
            float cosAngle = Vector3.Dot(newViewDir, up) / (newViewDir.Length() * up.Length());
            if (cosAngle < 0.999f && cosAngle > -0.999f)
            {
               eye = eyeLocal + target;
               setView(eye, target, up);
            }
        }

        public void panX(int value)
        {
            float scaleFactor = 0.0f;
            if (value > 1)
            {
                scaleFactor = -0.05f;
            }
            else if (value < -1 )
            {
                scaleFactor = 0.05f;
            }
            Vector3 viewDir = target - eye;
            Vector3 orhto = Vector3.Cross(viewDir, up);
            orhto.Normalize();
            scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
            Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
            orhto = Vector3.TransformCoordinate(orhto, scaling);
            
            target = target + orhto;
            eye = eye + orhto;
            setView(eye, target, up);
        }

        public void panY(int value)
        {
            float scaleFactor = 0.00f;
            if (value > 1)
            {
                scaleFactor = -0.05f;
            }
            else if (value < -1 )
            {
                scaleFactor = 0.05f;
            }
            Vector3 viewDir = target - eye;
            scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
            viewDir.Y = 0.0f;
            viewDir.Normalize();
            Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
            viewDir = Vector3.TransformCoordinate(viewDir, scaling);

            target = target + viewDir;
            eye = eye + viewDir;
            setView(eye, target, up);
        }


        float maxZoom = 3.0f;
        public void zoom(int value)
        {
            Vector3 viewDir = eye - target;

            float scaleFactor = 1.0f;
            if (value > 0)
            {
                scaleFactor = 1.1f;
            }
            else
            {
                if (viewDir.Length() > maxZoom)
                    scaleFactor = 0.9f;
            }

            Matrix scale = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
            viewDir.Normalize();
            viewDir = Vector3.TransformCoordinate(viewDir, scale);
            if (value > 0)
            {
                eye = eye + viewDir;
            }
            else
            {
                eye = eye - viewDir;
            }
            
            setView(eye, target, up);
        }
    }
}

The source code for the OrbitPanCamera is in large parts the same as for the OrbitCamera. New are the methods for translating in the x-direction (Method panX) and translating in the y-direction (Method panY) of the screen.
Lets take a look at the panX Method:

public void panX(int value)
{
  float scaleFactor = 0.0f;
  if (value > 1)
  {
    scaleFactor = -0.05f;
  }
  else if (value < -1)
  {
    scaleFactor = 0.05f;
  }
  Vector3 viewDir = target - eye;
  Vector3 orhto = Vector3.Cross(viewDir, up);
  orhto.Normalize();
  scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
  Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
  orhto = Vector3.TransformCoordinate(orhto, scaling);

  target = target + orhto;
  eye = eye + orhto;
  setView(eye, target, up);
}
The pose (position and orientation) is determined by the three vectors eye, target and up. The eye vector holds the current position of the camera, the target vector is the point to look at and the up vector defines the up direction. In order to pan sidewards, the general idea is to translate the position of the camera and the target to look at simultaneously. Therefore we calculate the direction we are looking (viewDir) and calculate the cross product with the up vector, which results in a vector that is pointing orthogonal to the viewDir vector.
This orthogonal vector is added to the target and eye vectors and we create a new view matrix, by calling setView. I perform scaling of the orthogonal vector depending in the distance to the target, so that the translation is little, if the camera is next to the target and bigger if the camera is far away.

The implementation of the panY method in analogue:
public void panY(int value)
{
  float scaleFactor = 0.00f;
  if (value > 1)
  {
    scaleFactor = -0.05f;
  }
  else if (value < -1)
  {
    scaleFactor = 0.05f;
  }
  Vector3 viewDir = target - eye;
  scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
  viewDir.Y = 0.0f;
  viewDir.Normalize();
  Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
  viewDir = Vector3.TransformCoordinate(viewDir, scaling);

  target = target + viewDir;
  eye = eye + viewDir;
  setView(eye, target, up);
}
This time we don't need the orthogonal vector but only the view direction of the camera. Again this vector is scaled corresposing to the distance to the target and added to the target vector and eye vector of the camera. Like above, the new view matrix is created with this new values.

Adapting the Mouse Event Handlers of the RenderControl

The only handlers we have to adapt are RenderControl_MouseMove and RenderControl_MouseWheel.
These are the handlers of the OrbitCamera:


private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
  if (dragging)
  {
    int currentX = e.X;
    deltaX = startX - currentX;
    startX = currentX;

    int currentY = e.Y;
    deltaY = startY - currentY;
    startY = currentY;

    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
      OrbitCamera.Instance.rotateY(-deltaX);
      OrbitCamera.Instance.rotateOrtho(deltaY);
    }
  }
}

void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
  int delta = e.Delta;
  OrbitCamera.Instance.zoom(delta);
}
We have to update the references from OrbitCamera to OrbitPanCamera and call the methods for panning in the RenderControl_MouseMove handler. I will use the right mouse button for panning:

private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
  if (dragging)
  {
    int currentX = e.X;
    deltaX = startX - currentX;
    startX = currentX;

    int currentY = e.Y;
    deltaY = startY - currentY;
    startY = currentY;

    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
      OrbitPanCamera.Instance.rotateY(-deltaX);
      OrbitPanCamera.Instance.rotateOrtho(deltaY);
    }
    else if (e.Button == System.Windows.Forms.MouseButtons.Right)
    {
      OrbitPanCamera.Instance.panX(deltaX);
      OrbitPanCamera.Instance.panY(deltaY);
    }
  }
}

void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
  int delta = e.Delta;
  OrbitPanCamera.Instance.zoom(delta);
}

Adapting the Renderables

We are not quite done yet, because we need the ViewPerspective matrix of our OrbitPanCamera to set the transformation in our Renderables. I will omit the code for this here, because it is just replacing the references to OrbitCamera to OrbitPanCamera in the Renderable classes.

In order to make the handling of cameras more flexible, I will introduce a CameraManager in the next tutorial, so we can have several cameras and do not need to hardcode the handling of mouse events and switching of cameras in the Renderables.

Result


You can download the source code here.

No comments:

Post a Comment