poniedziałek, 8 listopada 2010

[WPF 3D] Dopasowanie wielkości sceny do aktualnego widoku na ekranie [PL]

Niemal każdy program graficzny ma funkcjonalność: „dopasuj obraz do wielkości strony” (Zoom to fit, Auto-Zoom, itp ...). Zobaczmy jak można zrealizować taką funkcjonalność w programie pokazującym sceny 3D oparte o WPF 3D.
Niniejszy wpis poza prezentacją różnych i niezależnych od konkretnego wykorzystania treści stanowi również opis pewnych zagadnień związanych z projektem Mesh Diagram 3D.Informacje dotyczące projektu oznaczone są etykietą MeshDiagram3D.
Niniejszy wpis wykorzystuje techniki i fragmenty kodu, opisane przez Eric'a Sink'a w artykule „Auto-Zoom”, wchodzącego w skład serii „The Twelve Days of WPF 3D” (http://www.ericsink.com/wpf3d/index.html), którą gorąco polecam do przeczytania.
Jak więc zrealizować funkcjonalność „dopasuj obraz do wielkości strony” (Zoom to fit, Auto-Zoom, itp ...)? Zakładając wykorzystanie opisywanych wcześniej rozwiązań (w szczególności pochodzących z wpisu: „[WPF 3D] Interakcja z myszą – gotowy kod C#”), nie jest to takie trudne. Trzeba:
  1. Potrafić konstruować transformację odpowiadającą za skalowanie sceną (widokiem / kamerą).
  2. Wyznaczać bieżący rozmiar całej sceny (po projekcji jej na płaszczyznę).
  3. Sprawdzać, czy rozmiar sceny po jej projekcji na płaszczyznę jest większy od wykorzystywanego Viewportu3D i jeśli jest większy to odpowiednio zmieniać skalę. Natomiast w przypadku gdy rozmiar sceny po jej projekcji na płaszczyznę jest dużo mniejszy od wykorzystywanego Viewportu3D trzeba zmieniać jej skalę, tak aby uzyskać odpowiednie powiększenie.
Spójrzmy jednak na gotowy kod. Zacznijmy od wyznaczenia rozmiaru sceny po jej projekcji:
    /// <summary>
    /// Gets the 2D bounding box of all elements located on the view port 3d.
    /// </summary>
    /// <param name="viewPort3DVisual">The view port 3D.</param>
    /// <returns></returns>
    public static Rect Get2DBoundingBox( Viewport3DVisual viewPort3DVisual, out bool success )
    {
      bool transoformationMatrixCalculationFinishesOK;
      Matrix3D transoformationMatrix = MathUtils.TryWorldToViewportTransform( viewPort3DVisual,
          out transoformationMatrixCalculationFinishesOK );
      if ( !transoformationMatrixCalculationFinishesOK )
      {
        success = false;
        return new Rect();
      }
      bool firstPoint = true;
      success = true;
      Rect rectangleToBeReturned = new Rect();
      foreach ( Visual3D visual3D in viewPort3DVisual.Children )
      {
        if ( !success )
          break;
        ModelVisual3D modelVisual3D = visual3D as ModelVisual3D;
        if ( modelVisual3D != null )
        {
          if ( firstPoint )
          {
            rectangleToBeReturned = Get2DBoundingBox( modelVisual3D, viewPort3DVisual, transoformationMatrix, out success );
            firstPoint = false;
          }
          else
          {
            rectangleToBeReturned.Union( Get2DBoundingBox( modelVisual3D, viewPort3DVisual, transoformationMatrix, out success ) );
          }
        }

      }
      return rectangleToBeReturned;
    }
Funkcje pomocnicze (wykorzystane w powyższym i poniższym kodzie) pochodzą z pakietu 3DTools lub zostały opisane we wpisie: „[WPF 3D] Interakcja z myszą – gotowy kod C#”.
Zobaczmy teraz, jak może wyglądać funkcja sprawdzająca, czy aktualna scena nie jest zbyt duża dla naszego Viewport3D:
    /// <summary>
    /// Test whether curent 3D scene is too big for current Viewport3DVisual: vp3dv  .
    /// </summary>
    /// <param name="vp3dv">Current Viewport3DVisual.</param>
    /// <param name="success">if set to <c>true</c> the scene is too big for this Viewport.</param>
    /// <returns></returns>
    private static bool TooBig( Viewport3DVisual vp3dv, out bool success )
    {
      Rect r = MeshDiagram3DMath.Get2DBoundingBox( vp3dv, out success );
      if ( success && r.Height > 0 && r.Width > 0 )
      {
        if ( r.Left < 0 || r.Top < 0 || r.Right > vp3dv.Viewport.Width || r.Bottom > vp3dv.Viewport.Height )
          return true;
      }
      else
      {
        //The vieweport has 0 size
        success = false;
      }
      return false;
    }
A teraz samo sedno, czyli funkcja realizująca Auto-Zoom:
    private void AutoZoom()
    {
      bool success;
      Viewport3DVisual vp3dv = MeshDiagram3DMath.GetViewport3DVisual( mainViewport, out  success );
      if ( TooBig( vp3dv, out success ) && success )
      {
        while ( TooBig( vp3dv, out success ) && success )
        {
          Scale += 0.1;
        }
        while ( !TooBig( vp3dv, out success ) && Scale > 0 && success )
        {
          Scale -= 0.01;
        }
        Scale += 0.01;
      }
      else
      {
        while ( !TooBig( vp3dv, out success ) && success )
        {
          Scale -= 0.1;
          if ( Scale <= 0 )
          {
            Scale = 0.01;
            break;
          }
        }
        while ( TooBig( vp3dv, out success ) && success )
        {
          Scale += 0.01;
        }
      }
    }
Tutaj warto jeszcze zwrócić uwagę na to, czym jest zmienna „Scale”. Otóż w tym przypadku, na podstawie tej zmiennej wyznaczana jest transformacja kamery. Każda zmiana „Scale” powoduje ponowne wyznaczenie transformacji i ustawienie jej dla kamery, np.:
    public double Scale { get { return scale; } set { scale = value; SetCameraTransform(); } }
    private void SetCameraTransform()
    {
      mainViewport.Camera.Transform = PrepareTransform3D( 0, 0, 0, Scale, Scale, Scale, Rot_x, Rot_y, Rot_z );
      RaiseCameraIsChanged();
    }
Natomiast funkcja PrepareTransform3D, została wcześniej opisana we wpisie pt. „[WPF 3D] Obracamy, przesuwamy, skalujemy? Kamerę (i nie tylko).”.
Promuj

Brak komentarzy:

Prześlij komentarz

Posty powiązane / Related posts