6

こんにちは、現在 C++ で win32 アプリケーションを書いていますが、ウィンドウのコンテンツを拡大するのに本当に問題があります。ズームを行うために私が始めた疑似コードは次のとおりです。

// point One
int XPointOne = -200;
int YPointTwo = 0;

// point Two
int XPointTwo = 200;
int YPointTwo = 0;

// Draw point function.
DrawPoint(XCoordinate * ScalingFactor, YCoordinate * ScalingFactor) {
    ....
}

私の座標系は、原点がウィンドウの中心になるように設定されています。マウスホイールを使用したときにズームしたいと思います。上記のソリューションの問題は、ズームが常にウィンドウの中心から発生することです。マウスがウィンドウの中央にない場合、これは見苦しく見えます。マウスがある領域にズームしたいのですが、x 方向と y 方向のオフセットを計算する適切なアルゴリズムが見つかりません。たとえば、マウスの座標が (-200, 0) の場合、ポイント 1 は座標 (-200, 0) で、ポイント 2 は座標 (600, 0) で、倍率は 2 です。私はすでに多くのことを試しましたが、特にズーム中にマウスが他の場所に移動したときにすべてが台無しになると、うまくいきませんでした。誰でもこの問題を解決する方法を知っていますか?

これが私のアプリケーションのコード例です。最初のスニペットは、WM_MOUSEWHEEL メッセージを処理するためのコールバック関数です。

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam) {
    if(GET_WHEEL_DELTA_WPARAM(WParam) > 0)
    {
        // Zoom in
        Draw.ScaleFactor += 0.1;
    }
    else
    {
         // Zoom out
    }
}

Draw は、単に GDI 関数をラップするクラスです。倍率メンバーがあります。以下のスニペットは、倍率を使用して円を画面に正しく表示する Draw オブジェクトの DrawCircle メンバー関数です。

VOID DrawCircle(DOUBLE const& XCoordinate, DOUBLE const& YCoordinate, DOUBLE const& Radius, COLORREF const& Color) {
    HBRUSH Brush = CreateSolidBrush(Color);
    HBRUSH OldBrush = (HBRUSH)SelectObject(this->MemoryDC, Brush);

    Ellipse(this->MemoryDC, (INT) ((XCoordinate - Radius) * this->ScaleFactor), 
        -(INT)((YCoordinate + Radius) * this->ScaleFactor), 
         (INT)((XCoordinate + Radius) * this->ScaleFactor), 
        -(INT)((YCoordinate - Radius) * this->ScaleFactor)); 
    SelectObject(this->MemoryDC, OldBrush);
    DeleteObject(Brush);
 }

ご覧のとおり、私の DrawCircle 関数は、現在の倍率を適用するときにマウスの位置を考慮していません。

編集

解決策に近づいたのは、マウス コールバック関数の更新版です。

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam) {
    // Get Mouse position in real coordinates and not window coordinates.
    INT XOffset = (Window.GetClientWidth() / -2) + XMousePos;
    INT YOffset = (Window.GetClientHeight() / 2) - YMousePos;


    if(GET_WHEEL_DELTA_WPARAM(WParam) > 0)
    {
        Draw.ScaleFactor += 0.1;
        Draw.XOffsetScale = -XOffset * (Draw.ScaleFactor - 1.0);
        Draw.YOffsetScale = YOffset * (Draw.ScaleFactor - 1.0);
    }
    else
    {
        // ...
    }
}

そして、これが円を描く関数です。

VOID DrawCircle(DOUBLE const& XCoordinate, DOUBLE const& YCoordinate, DOUBLE const& Radius, COLORREF const& Color) {
        HBRUSH Brush = CreateSolidBrush(Color);
        HBRUSH OldBrush = (HBRUSH)SelectObject(this->MemoryDC, Brush);

        Ellipse(this->MemoryDC, (INT) ((XCoordinate - Radius) * this->ScaleFactor + XOffsetScale) , 
            -(INT)((YCoordinate + Radius) * this->ScaleFactor - YOffsetScale), 
            (INT)((XCoordinate + Radius) * this->ScaleFactor + XOffsetScale), 
            -(INT)((YCoordinate - Radius) * this->ScaleFactor - YOffsetScale)); 
        SelectObject(this->MemoryDC, OldBrush);
        DeleteObject(Brush);
 }

これは、マウスを同じ位置に置いている限り機能しますが、別の位置に移動すると、期待どおりにズームしなくなり、その後再び正しくズームします。多分これは少し役立ちます。

前もって感謝します!

解決済み

わかりました、私は今私の問題を解決しました。マウスの位置にスケーリング係数を掛けて、座標系の原点を移動しました。回答ありがとうございます。

4

3 に答える 3

9

最も「一般的な」ソリューションは行列変換を使用しますが、ここでは簡単に説明します。次の擬似コードが役立つ場合があります。

/*
    VARIABLES (all in space coordinates, not pixel coordinates):

      input:
        viewRect = rectangle of the viewed area
        zoomFactor = factor of zoom relative to viewRect, ex 1.1
        mousePos = position of the mouse

      output:
        zoomedRect = viexRect after zoom
*/

/*
    A little schema:

      viewRect
    *-----------------------------------------------------------------------*
    |                       ^                                               |
    |                       | d_up                                          |
    |        zoomedRect     v                                               |
    |      *-----------------------------------------*                      |
    |d_left|                                         |       d_right        |
    |<---->|                mousePos                 |<-------------------->|
    |      |                    +                    |                      |
    |      |                                         |                      |
    |      |                                         |                      |
    |      *-----------------------------------------*                      |
    |                       ^                                               |
    |                       |                                               |
    |                       |                                               |
    |                       | d_down                                        |
    |                       |                                               |
    |                       v                                               |
    *-----------------------------------------------------------------------*

    dX = d_left + d_right
    dY = d_up + d_down
    The origin of rects is the upper left corner.
*/

/*
    First, find differences of size between zoomed rect and original rect
    Here, 1 / zoomFactor is used, because computations are made relative to the
    original view area, not the final rect):
*/
dX = viewRect.width * (1 - 1 / zoomFactor)
dY = viewRect.height * (1 - 1 / zoomFactor)

/*
    Second, find d_* using the position of the mouse.
    pX = position of the mouse along X axis, relative to viewRect (percentage)
    pY = position of the mouse along Y axis, relative to viewRect (percentage)
    The value of d_right and d_down is not computed because is not directly needed
    in the final result.
*/
pX = (mousePos.X - viewRect.X) / viewRect.width
pY = (mousePos.Y - viewRect.Y) / viewRect.height

d_left = pX * dX
d_up = pY * dY

/*
    Third and last, compute the output rect
*/
zoomedRect = viewRect
zoomedRect.X += d_left
zoomedRect.Y += d_up
zoomedRect.width -= dX
zoomedRect.height -= dY

// That's it!

問題については、ビュー(ウィンドウ)をシーン(描画されるオブジェクト)から分離する必要があります。シーンの一部(またはすべて)を描画する関数が必要です。

void drawScene(Rect viewArea);

および領域をズームする関数(前に示したアルゴリズムを使用):

Rect zoomArea(Rect rectToZoom, Point zoomCenter, double factor);

これで、コールバックがはるかに簡単になります。

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam)
{
    // Get the position of the mouse relative to the window (in percent)
    double XMouseRel = XMousePos / double(Window.GetClientWidth());
    double YMouseRel = YMousePos / double(Window.GetClientHeight());

    // Get Mouse position in scene coordinates and not window coordinates.
    // viewArea is in scene coordinates
    // window = your window or your draw information on the scene
    // The following assumes that you're using a scene with X left-to-right and
    // Y top-to-bottom.
    double XMouse = window.viewArea.width * XMouseRel + window.viewArea.upperleft.X;
    double YMouse = window.viewArea.height * YMouseRel + window.viewArea.upperleft.Y;

    // Zoom parameters
    double zFactor = 0.1 * GET_WHEEL_DELTA_WPARAM(WParam);
    Rect viewArea = getViewArea(); // or something like this
    Point zCenter(XMouse,YMouse);

    // Zoom
    Rect zoomedRect = zoomArea(viewArea,zCenter,zFactor);
    drawScene(zoomedRect);
}
于 2012-11-09T23:15:28.010 に答える
7

平面にアフィン変換のサブセットを実装しようとしています。あなたの場合、描画面の平行移動とスケーリング (ズーム) を組み合わせるだけで済みます。平面でのアフィン変換の可能性の完全なセットには、3 次元の行列の使用が含まれますが、今のところ、問題に必要な最小限のものを示します。ネットでトピック全体を自由に調べてください。これに関する文献はたくさんあります。

まず、2D ベクトルといくつかの演算子を宣言します。

  class vector2D {
  protected:
      /* here your class implementation details */
  public:
      vector2D(const vector2D &v);
      vector2D(float x, float y) { /* ... */ }
      vector2D operator +(const vector2D &v) const { /* ... */ }
      vector2D operator -(const vector2D &v) const { /* ... */ }
      vector2D operator *(float v) const { /* ... */ }
      bool operator ==(const vector2D &v) const { /* ... */ }
      const vector2D &operator = (const vector2D &v) { /* ... */ }
  };

空白を埋めるか、独自のクラスがある場合はそれを使用します。このインターフェイスは最適ではない可能性があることに注意してください。ただし、パフォーマンスではなく、アルゴリズムに集中したいと考えています。

それでは、表示変換について説明しましょう。

zfズーム係数、trans変換の平行移動部分origin、原点をウィンドウ内のビューと呼びます。あなたの座標系はウィンドウの中心にあると述べたので、原点はウィンドウ画面の中心になります。ビュー システムからウィンドウ座標への変換は、2 つの段階に分解できます。1 つは、表示されたオブジェクトのズームと変換であり、これをmodelviewと呼びます。もう 1 つは、ビュー座標からへの変換です。これをプロジェクションと呼びます。3D レンダリングに精通している場合、これは OpenGL で使用されるものと類似したメカニズムと見なすことができます。

投影は、ウィンドウの左上からoriginビューの への単純な変換として説明できます。

  vector2D project(const vector2D &v){
      return v + origin;
  }

モデルビューは移動とズームを組み合わせます (この時点では、UI コードは任意のポイントでのズームのみを処理します)。

  vector2D modelview(const vector2D &v){
      return trans + (v * zf);
  }

これらの関数と関連データ ( zfcentretrans) を最も便利な方法で整理します。

次に、さまざまなデータが UI によってどのように変更されるかを見てみましょう。

基本的に、ポイント座標をビューの中心に配置された座標系からズームポイントを中心としたシステムに変更し、新しい座標をスケーリングしてから、ビューの中心に戻る必要があります。描画する各オブジェクトは、この変換を受ける必要があります。

式は次のとおりです。

v' = (v + zp) * s - zp

ここで、zpはズーム ポイント、sは倍率、vは変換されるシステム内のポイントの座標です。したがって、v'は結果のズーム ポイントです。

さまざまな場所でズームを連鎖させたい場合は、前のズーム係数と中心を考慮する必要があります。

cが新しいズームの中心、tが現在の平行移動、zが現在のズーム係数、z2が新しいズーム係数である場合、次のように新しいグローバル変換を計算できます。

t' = t + c * (1 - z2) z' = z * z2

これらは、座標系をズームの中心に移動し、変換にズームを適用し、原点に戻ることによって得られます。

ズームの中心に関しては、マウス入力がウィンドウ座標系になるため、ビュー システムに変換し直す必要があることに注意する必要があります (中心にあるorigin)。次のunproject関数はまさにそれを行います。

 vector2D unproject(const vector2D &v){
     return v - origin;
 }

最後に、新しい入力に従ってモデルビュー変換を変換する関数の簡単な実装をしましょう。

 void onMouseWheel(float mouseX, float mouseY, bool zoom_in){
     float z2 = zoom_in? 1.1 : 1/1.1;
     vector2D m(mouseX,mouseY);
     if (! (m == origin)) { // this is very likely
         trans = trans + unproject(m) * (1.0 - z2);
     }
     zf *= z2;
     // here perhaps have a redraw event fired
 }

ご覧のとおり、多かれ少なかれ汎用的なコードを提供しました。これは、Win32 API の特異性に適応させる必要があります。

于 2012-11-10T00:57:26.480 に答える
0

あなたがしたいことは、マウスポイントが(0,0)になるように座標を変換し、座標をズームしてから、オフセット(0,0)をマウス座標に戻すことです。

Ellipse(this->MemoryDC, (INT) (((XCoordinate - XMouse) - Radius) * this->ScaleFactor) + XMouse, 
    -(INT)(((YCoordinate - YMouse) + Radius) * this->ScaleFactor) + YMouse, 
     (INT)(((XCoordinate - XMouse) + Radius) * this->ScaleFactor) + XMouse, 
    -(INT)(((YCoordinate - YMouse) - Radius) * this->ScaleFactor) + YMouse); 
于 2012-11-09T23:22:53.887 に答える