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:
- 3D Tools: http://3dtools.codeplex.com/
- seria wpisów autorstwa Eric'a Sink'a: „The Twelve Days of WPF 3D” (http://www.ericsink.com/wpf3d/index.html), a w szczególności: „8: Mouse Handling” oraz „10: Auto-Zoom”
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 ); }
Witam. Nigdzie nie ma kodu ciała funkcji:
OdpowiedzUsuńGet2DBoundingBoxFromPoint3DCollection
znajduje sie w Mesh Diagram 3D : Utils : SimpleHelperClasses.cs
Usuńhttp://meshdiagram3d.codeplex.com/SourceControl/changeset/view/64403#1674214