1

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("");
        }
    }
}

}

4

1 回答 1

2

您的问题来自这段代码:

1.0f + scale

让我们以单个像素的移动为例。正如你所拥有m_scaleProportion = 0.01的那样,当你缩小时,你乘以0.99。当你放大你乘以1.01

如果你放大一个像素然后缩小一个像素会发生什么?好吧,本质上,您将这两个值相乘。像这样:0.99 × 1.01 = 0.9999。这小于 1。重复此操作,最终您的缩放量会越来越小。


如何正确实现缩放:

首先:我建议不要在大多数情况下通过乘法来累加值(即:根据输入修改多帧的值)。在您的代码中,您不断地乘以矩阵(就像您正在做的那样*=) - 这可能会慢慢导致浮点问题。(所以:不要用矩阵做,也不要用常规做float

您应该做的是通过加法来累积鼠标移动。然后根据到目前为止的整体运动更新矩阵。像这样的东西(伪代码):

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));
}

注意使用Math.Pow函数来提供实际的比例因子。这个函数很好,因为它为负输​​入提供小于 1 的值,为正输入提供大于 1 的值。对于零输入,它给出 1(无缩放)。

而且,当然,您可能希望将您的数据透视代码添加到上面。这将是相似的。您可能还需要调整缩放率。

注意:如果您使用此矩阵进行 2D 工作SpriteBatch,请不要缩放 Z 轴。仅缩放 X 和 Y 轴并保留 Z = 1。


旁白:您可能会注意到,从技术上讲,我上面的代码是通过乘法累加的,但只有在缩放完成时(所以它几乎没有那么糟糕)。在我自己的代码中,我通常会始终存储缩放量(从不将其重置为零)并重新生成完整的矩阵——它更健壮一些。

于 2012-08-10T14:29:56.657 回答