piątek, 5 listopada 2010

[WPF 3D] Interakcja z myszą – gotowy kod C# [PL]

W poprzednim wpisie opisany został algorytm, jakim można się kierować przy wypracowywaniu rozwiązania pozwalającego na interakcję myszy ze sceną 3D. W tym wpisie zaprezentowany zostanie gotowy kod w języku C# pozwalający na osiągnięcie założonego celu.
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.
W przygotowaniu poniższego kodu wspierałem się następującymi źródłami:

A więc zaczynamy...

Po pierwsze! Dla wybranego elementu sceny (typu ModelVisual3D) musimy znaleźć Viewport3DVisual, by móc określić płaszczyznę, na jaką element jest rzutowany:
    public static Rect Get2DBoundingBox( ModelVisual3D modelVisual3D, out bool success )
    {
      if ( modelVisual3D != null )
      {
        Viewport3DVisual vp3dv = GetViewport3DVisual( modelVisual3D, out success );
        if ( success )
          return Get2DBoundingBox( modelVisual3D, vp3dv, out success );
      }
      success = false;
      return new Rect();
    }
W powyższym kodzie funkcja odnajdująca Viewport3DVisual została opisana wcześniej we wpisie pt. „Wizualne drzewo w WPF (visual tree) a elementy w scenie 3D” i wygląda następująco:
    public static Viewport3DVisual GetViewport3DVisual( DependencyObject visual, out bool success )
    {
      if ( visual is Viewport3D )
      {
        //when Viewport3D is passed first children is taken for further processing
        visual = ( (Viewport3D)visual ).Children[ 0 ];
      }
      Viewport3DVisual viewport;
      if ( !( visual is Visual3D ) )
      {
        throw new ArgumentException( "Must be of type Visual3D or Viewport3D.", "visual" );
      }
      while ( visual != null )
      {
        if ( !( visual is ModelVisual3D ) )
        {
          break;
        }
        visual = VisualTreeHelper.GetParent( visual );
      }
      viewport = visual as Viewport3DVisual;
      if ( viewport == null && visual != null )
      {
        // In WPF 3D v1 the only possible configuration is a chain of
        // ModelVisual3Ds leading up to a Viewport3DVisual.
        throw new ApplicationException(
            String.Format( "Unsupported type: '{0}'.  Expected tree of ModelVisual3Ds leading up to a Viewport3DVisual.",
            visual.GetType().FullName ) );
      }
      if ( viewport == null )
        success = false;
      else
        success = true;
      return viewport;
    }
Po znalezieniu elementu nadrzędnego (Viewport3DVisual), możemy przejść do wyznaczenia macierzy transformacji punktów sceny 3D do płaszczyzny obserwacji:
    private static Rect Get2DBoundingBox( ModelVisual3D modelVisual3D, Viewport3DVisual viewPort3DVisual, out bool success )
    {
      if ( modelVisual3D == null )
        throw new ArgumentNullException( "modelVisual3D" );
      if ( viewPort3DVisual == null )
        throw new ArgumentNullException( "viewPort3DVisual" );
      bool transoformationMatrixCalculationFinishesOK;
      Matrix3D transoformationMatrix = MathUtils.TryWorldToViewportTransform( viewPort3DVisual,
          out transoformationMatrixCalculationFinishesOK );
      if ( !transoformationMatrixCalculationFinishesOK )
      {
        success = false;
        return new Rect();
      }
      return Get2DBoundingBox( modelVisual3D, viewPort3DVisual, transoformationMatrix, out success );
    }
Po wyznaczeniu płaszczyzny przechodzimy do wyznaczania prostokątów, które zawierają poszczególne elementy w scenie. W zależności od typu elementu (GeometryModel3D, Model3DGroup, ...), konieczne jest różne podejście do zagadnienia:
    private static Rect Get2DBoundingBox( ModelVisual3D modelVisual3D, Viewport3DVisual viewPort3DVisual, Matrix3D transoformationMatrix, out bool success )
    {
      bool firstPoint = true;
      Rect rectangleToBeReturned = new Rect();
      if ( modelVisual3D.Content is GeometryModel3D )
      {
        GeometryModel3D geometryModel3D =
            (GeometryModel3D)modelVisual3D.Content;

        if ( geometryModel3D.Geometry is MeshGeometry3D )
        {
          MeshGeometry3D mg3d =
              (MeshGeometry3D)geometryModel3D.Geometry;
          Get2DBoundingBoxFromPoint3DCollection( transoformationMatrix, ref firstPoint, ref rectangleToBeReturned, mg3d.Positions );
        }
      }
      else if ( modelVisual3D.Content is Model3DGroup )
      {
        Model3DGroup m3dg = (Model3DGroup)modelVisual3D.Content;
        Rect3D myR3D = m3dg.Bounds;
        Get2DBoundingBoxFromPoint3DCollection( transoformationMatrix, ref firstPoint, 
          ref rectangleToBeReturned, GetPoint3DCollectionFromRect3D( myR3D ) );
      }
      success = true;
      foreach ( ModelVisual3D child in modelVisual3D.Children )
      {
        if ( !success )
          break;
        if ( firstPoint )
        {
          rectangleToBeReturned = Get2DBoundingBox( child, viewPort3DVisual, out success );
          firstPoint = false;
        }
        else
        {
          rectangleToBeReturned.Union( Get2DBoundingBox( child, viewPort3DVisual, out success ) );
        }
      }
      return rectangleToBeReturned;
    }
Wykorzystane powyżej funkcje pomocnicze Get2DBoundingBoxFromPoint3DCollection, oraz GetPoint3DCollectionFromRect3D, wyglądają następująco:
    private static Point3DCollection GetPoint3DCollectionFromRect3D( Rect3D myR3D )
    {
      Point3DCollection P3DCollection = new Point3DCollection();
      P3DCollection.Add( new Point3D( myR3D.X, myR3D.Y, myR3D.Z ) );
      P3DCollection.Add( new Point3D( myR3D.X + myR3D.SizeX, myR3D.Y, myR3D.Z ) );
      P3DCollection.Add( new Point3D( myR3D.X, myR3D.Y + myR3D.SizeY, myR3D.Z ) );
      P3DCollection.Add( new Point3D( myR3D.X, myR3D.Y, myR3D.Z + myR3D.SizeZ ) );
      P3DCollection.Add( new Point3D( myR3D.X + myR3D.SizeX, myR3D.Y + myR3D.SizeY, myR3D.Z ) );
      P3DCollection.Add( new Point3D( myR3D.X + myR3D.SizeX, myR3D.Y + myR3D.SizeY, myR3D.Z + myR3D.SizeZ ) );
      P3DCollection.Add( new Point3D( myR3D.X, myR3D.Y + myR3D.SizeY, myR3D.Z + myR3D.SizeZ ) );
      P3DCollection.Add( new Point3D( myR3D.X + myR3D.SizeX, myR3D.Y + myR3D.SizeY, myR3D.Z + myR3D.SizeZ ) );
      return P3DCollection;
    }
    private static Point Get2DPointBasedOnPoint3DAndTRansformation( Matrix3D transoformationMatrix, Point3D point3D )
    {
      Point3D point3Dtransformed = transoformationMatrix.Transform( point3D );
      Point point2D = new Point( point3Dtransformed.X, point3Dtransformed.Y );
      return point2D;
    }
Uff .... trochę kodu się tu pojawiło, ale teraz już można wykonać operację podobną do:
    bool Match( Point point2D )
    {
      bool success = false;
      Rect my2DRect = Get2DBoundingBox( MyModelVisual3D,out success );
      if ( !success )
        return false;
      return my2DRect.Contains( point2D );
    }
Promuj

2 komentarze:

  1. Witam. Nigdzie nie ma kodu ciała funkcji:
    Get2DBoundingBoxFromPoint3DCollection

    OdpowiedzUsuń

Posty powiązane / Related posts