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