Reputation: 1321
I did this camera class for "debugging" my graphics, it works fine, but I dont get why it ends up zooming out more then in.
I implemented a zoom functionality that works by dragging the screen with the right mouse button(RMB) (with the left you really drag the screen around), it works like the Unity editor zoom (when you hold alt and use theRMB, its a zoom functionality)
The thing is, if I hold the RMB and shake it like a moron, the zooming will zoom out more than zoom in, and eventually let everything tinny....I know its kinda stupid worry of mine, but means theres something not very precise there.. in the unity editor, shaking it like a moron will not make things eventually get super zoomed out...
Can anyone tell me what is causing this behaviour? Kinda tricky..heres my class, dont mind the simpleness, i just throwed it for debugging:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace Shooter
{
/// <summary>
/// Provides a camera controllable by mouse.
/// Controls include dragging the "screen"...
/// </summary>
class MouseCamera2D
{
private Matrix m_mView; // stores the camera state (position, scale/zoom, rotation)
public Matrix ViewMatrix { get { return m_mView; } }
public Vector3 GetPosition() { return m_mView.Translation; }
public float m_scaleProportion = 0.01f; // the "speed" to scale, multiplies the offset from mouse drag
// input data
private MouseState m_mouseStateRef;
private MouseState m_mouseStatePreviows = new MouseState();
private KeyboardState m_kbStateRef;
private KeyboardState m_kbStatePreviows = new KeyboardState();
public Keys m_resetZoomKey = Keys.Z;
//private GraphicsDevice m_graphicsDeviceRef;
// ctor
public MouseCamera2D(MouseState mouseState_p, KeyboardState kbState_p/*, GraphicsDevice graphicsDevice_p*/){
if (mouseState_p == null ||kbState_p == null /*|| graphicsDevice_p == null*/) throw new Exception("cant be null"); // needs the reference
//m_graphicsDeviceRef = graphicsDevice_p;
m_mouseStateRef = mouseState_p; // init the reference
m_kbStateRef = kbState_p;
m_mView = Matrix.Identity; // set a valid matrix
//zoomPivot = new Vector2(m_graphicsDeviceRef.Viewport.Width * 0.5f, m_graphicsDeviceRef.Viewport.Height * 0.5f);
}
public void InputUpdate()
{
m_mouseStatePreviows = m_mouseStateRef;
m_mouseStateRef = Mouse.GetState();
m_kbStatePreviows = m_kbStateRef;
m_kbStateRef = Keyboard.GetState();
InputDragControl();
InputScaleControl();
InputZoomOriginal();
}
private void InputDragControl()
{
//mouseStatePreviows = mouseStateRef;
// mouseStateRef = Mouse.GetState();
if (m_mouseStateRef.LeftButton == ButtonState.Pressed)
{
// check if its a drag or a new pivot point
if (m_mouseStatePreviows.LeftButton == ButtonState.Pressed)
{
m_mView.M41 += (m_mouseStateRef.X - m_mouseStatePreviows.X);
m_mView.M42 += (m_mouseStateRef.Y - m_mouseStatePreviows.Y);
}
}
}
Vector3 zoomPivot;
private void InputScaleControl()
{
if (m_mouseStateRef.RightButton == ButtonState.Pressed)
{
// check if its a drag or a new pivot point
if (m_mouseStatePreviows.RightButton == ButtonState.Pressed)
{
float scale = ((m_mouseStateRef.X - m_mouseStatePreviows.X) + (m_mouseStateRef.Y - m_mouseStatePreviows.Y)); //compute distance with "1d direction"(not abs)
if (scale != 0.0f)
{
scale *= m_scaleProportion;
//center zoom on mouse cursor:
m_mView *=
Matrix.CreateTranslation(-zoomPivot)
*
Matrix.CreateScale(1.0f + scale)
*
Matrix.CreateTranslation(zoomPivot)
;
Console.WriteLine(scale.ToString());
Console.WriteLine(m_mView.M11.ToString());
Console.WriteLine("");
}
}
else
{
// new press, get pivot point:
zoomPivot.X = m_mouseStateRef.X;
zoomPivot.Y = m_mouseStateRef.Y;
}
}
}
private void InputZoomOriginal()
{
if( m_kbStateRef.IsKeyDown(m_resetZoomKey) )
{
m_mView *=
Matrix.CreateTranslation(-zoomPivot)
*
Matrix.CreateScale(1.0f/m_mView.M11)
*
Matrix.CreateTranslation(zoomPivot)
;
Console.WriteLine(m_mView.M11.ToString());
Console.WriteLine("");
}
}
}
}
Upvotes: 1
Views: 925
Reputation: 27215
Your problem comes from this bit of code:
1.0f + scale
Let's take the movement of a single pixel. With m_scaleProportion = 0.01
, as you have it, when you zoom out you're multiplying by 0.99. When you zoom in you're multiplying by 1.01.
What happens if you zoom in by one pixel, then out by one pixel? Well you multiply the two values, essentially. Like so: 0.99 × 1.01 = 0.9999. This is less than 1. Repeat this and eventually your zoom amount will get smaller and smaller.
How to implement zoom properly:
First of all: I recommend against accumulating values by multiplication in most cases (ie: modifying the value over many frames based on input). In your code you are multiplying the matrix (as you are doing with *=
) constantly - which can slowly lead to floating point issues. (So: don't do it with a matrix, but also don't do it with a regular float
)
What you should do is accumulate the mouse movement by addition. And then update the matrix based on the overall movement so far. Something like this (pseudocode):
if(mouseWentDown)
{
// store the starting matrix:
this.originalMatrix = this.currentMatrix;
int zoomAmount = 0;
}
else if(mouseIsDown)
{
// Accumulate the mouse movment:
zoomAmount += (how far the mouse moved this frame);
// Recalculate the matrix based on the starting matrix:
this.currentMatrix = this.originalMatrix *
Matrix.CreateScale((float)Math.Pow(2, zoomAmount * zoomRate));
}
Notice the use of the Math.Pow
function to provide the actual scaling factor. This function is nice because it gives a value less than 1 for negative inputs, and a value greater than 1 for positive inputs. For an input of zero it gives 1 (no scaling).
And, of course, you'll probably want to add in your pivot code to the above. It will be similar. You might need to tune the zoom rate too.
NOTE: If you are using this matrix for 2D work using SpriteBatch
, don't scale the Z axis. Only scale the X and Y axes and leave Z = 1.
Aside: You may note that technically my above code is accumulating by multiplication, but only when the zoom is completed (so it's not nearly as bad). In my own code I will generally always store the zoom amount (never resetting it to zero) and regenerate the full matrix - it's a bit more robust.
Upvotes: 2