0
\$\begingroup\$

I've been following the guide here for how to create a custom 2D camera in XNA. It works great, I've implemented it before, but for some reason, the matrix math is throwing me off.

public sealed class Camera2D
{
    public Vector2 Origin { get; set; }
    public Vector2 Position { get; set; }
    public float Scale { get; set; }
    public float Rotation { get; set; }
}

It might be easier to just show you a picture of my problem: http://i.imgur.com/H1l6LEx.png

What I want to do is allow the camera to pivot around any given point. Right now, I have the rotations mapped to my shoulder buttons on a gamepad, and if I press them, it should rotate around the point the camera is currently looking at. Then, I use the left stick to move the camera around. The problem is that after it's been rotated, pressing "up" results in it being used relative to the rotation, creating the image above.

I understand that matrices have to be applied in a certain order, and that I have to offset the thing to be rotated around the world origin and move it back, but it just won't work!

public Matrix GetTransformationMatrix()
    {
        Matrix mRotate = Matrix.Identity *
            Matrix.CreateTranslation(-Origin.X, -Origin.Y, 0.00f) *  // Move origin to world center
            Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)) * // Apply rotation
            Matrix.CreateTranslation(+Origin.X, +Origin.Y, 0.00f);   // Undo the move operation

        Matrix mTranslate = Matrix.Identity *
            Matrix.CreateTranslation(-Position.X, Position.Y, 0.00f); // Apply the actual translation

        return mRotate * mTranslate;
    }

So to recap, it seems I can have it rotate around an arbitrary point and lose the ability to have "up" move the camera straight up, or I can rotate it around the world origin and have the camera move properly, but not both.

\$\endgroup\$
1
\$\begingroup\$

Though Fault's comment is correct, what I usually do is to store the rotation and translation and then recreate the view matrix when required. (Full article here).

The gist of the idea is: (btw the type of camera you are creating is often referred to as an Arc-Ball camera.)

public class ArcBallCamera
    {


    public ArcBallCamera(float aspectRation, Vector3 lookAt)
        : this(aspectRation, MathHelper.PiOver4, lookAt, Vector3.Up, 0.1f, float.MaxValue) { }

    public ArcBallCamera(float aspectRatio, float fieldOfView, Vector3 lookAt, Vector3 up, float nearPlane, float farPlane)
    {
        this.aspectRatio = aspectRatio;
        this.fieldOfView = fieldOfView;            
        this.lookAt = lookAt;
        this.nearPlane = nearPlane;
        this.farPlane = farPlane;
    }

    /// <summary>
    /// Recreates our view matrix, then signals that the view matrix
    /// is clean.
    /// </summary>
    private void ReCreateViewMatrix()
    {
        //Calculate the relative position of the camera                        
        position = Vector3.Transform(Vector3.Backward, Matrix.CreateFromYawPitchRoll(yaw, pitch, 0));
        //Convert the relative position to the absolute position
        position *= zoom;
        position += lookAt;

        //Calculate a new viewmatrix
        viewMatrix = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
        viewMatrixDirty = false;
    }

    /// <summary>
    /// Recreates our projection matrix, then signals that the projection
    /// matrix is clean.
    /// </summary>
    private void ReCreateProjectionMatrix()
    {
        projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, AspectRatio, nearPlane , farPlane);
        projectionMatrixDirty = false;
    }

    #region HelperMethods

    /// <summary>
    /// Moves the camera and lookAt at to the right,
    /// as seen from the camera, while keeping the same height
    /// </summary>        
    public void MoveCameraRight(float amount)
    {
        Vector3 right = Vector3.Normalize(LookAt - Position); //calculate forward
        right = Vector3.Cross(right, Vector3.Up); //calculate the real right
        right.Y = 0;
        right.Normalize();
        LookAt += right * amount;
    }

    /// <summary>
    /// Moves the camera and lookAt forward,
    /// as seen from the camera, while keeping the same height
    /// </summary>        
    public void MoveCameraForward(float amount)
    {
        Vector3 forward = Vector3.Normalize(LookAt - Position);
        forward.Y = 0;
        forward.Normalize();
        LookAt += forward * amount;
    }

    #endregion

    #region FieldsAndProperties
    //We don't need an update method because the camera only needs updating
    //when we change one of it's parameters.
    //We keep track if one of our matrices is dirty
    //and reacalculate that matrix when it is accesed.
    private bool viewMatrixDirty = true;
    private bool projectionMatrixDirty = true;

    public float MinPitch = -MathHelper.PiOver2 + 0.3f;
    public float MaxPitch = MathHelper.PiOver2 - 0.3f;
    private float pitch;
    public float Pitch
    {
        get { return pitch; }
        set
        {
            viewMatrixDirty = true;
            pitch = MathHelper.Clamp(value, MinPitch, MaxPitch);               
        }
    }

    private float yaw;
    public float Yaw
    {
        get { return yaw; }
        set
        {
            viewMatrixDirty = true;
            yaw = value;
        }
    }

    private float fieldOfView;
    public float FieldOfView
    {
        get { return fieldOfView; }
        set
        {
            projectionMatrixDirty = true;
            fieldOfView = value;
        }
    }

    private float aspectRatio;
    public float AspectRatio
    {
        get { return aspectRatio; }
        set
        {
            projectionMatrixDirty = true;
            aspectRatio = value;
        }
    }

    private float nearPlane;
    public float NearPlane
    {
        get { return nearPlane; }
        set
        {
            projectionMatrixDirty = true;
            nearPlane = value;
        }
    }

    private float farPlane;
    public float FarPlane
    {
        get { return farPlane; }
        set
        {
            projectionMatrixDirty = true;
            farPlane = value;
        }
    }

    public float MinZoom = 1;
    public float MaxZoom = float.MaxValue;
    private float zoom = 1;
    public float Zoom
    {
        get { return zoom; }
        set
        {
            viewMatrixDirty = true;
            zoom = MathHelper.Clamp(value, MinZoom, MaxZoom);
        }
    }


    private Vector3 position;
    public Vector3 Position
    {
        get
        {
            if (viewMatrixDirty)
            {
                ReCreateViewMatrix();
            }
            return position;
        }
    }

    private Vector3 lookAt;
    public Vector3 LookAt
    {
        get { return lookAt; }
        set
        {
            viewMatrixDirty = true;
            lookAt = value;
        }
    }
    #endregion

    #region ICamera Members        
    public Matrix ViewProjectionMatrix
    {
        get {return ViewMatrix * ProjectionMatrix; }
    }

    private Matrix viewMatrix;        
    public Matrix ViewMatrix
    {
        get
        {
            if (viewMatrixDirty)
            {
                ReCreateViewMatrix();
            }
            return viewMatrix;
        }
    }

    private Matrix projectionMatrix;
    public Matrix ProjectionMatrix
    {
        get
        {
            if (projectionMatrixDirty)
            {
                ReCreateProjectionMatrix();
            }
            return projectionMatrix;
        }
    }
    #endregion
}
\$\endgroup\$
  • 1
    \$\begingroup\$ At the end of the day, does a camera need to actually do anything else other than expose a Matrix? My plan was to create an ICamera interface that had a view matrix, then create ie, an ArcBallCamera, an OrthogonalCamera, etc that handled things differently. \$\endgroup\$ – Kyle Baran Aug 25 '14 at 14:16
  • 2
    \$\begingroup\$ Fun fact: in my own engine this class implements my ICamera interface which does exactly what you're saying. \$\endgroup\$ – Roy T. Aug 26 '14 at 8:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.