2013-01-08 3 views
5

Dodałem kilka modułów do Viewport3D w WPF i teraz chcę nimi manipulować za pomocą myszy.Obróć grupę modeli zgodnie z kierunkiem i położeniem przeciągania myszy w modelu

initial cube

Kiedy klikam & przeciągnij po jednej i połowę z tych kostek chcę płaszczyzny otworu obraca się w kierunku, że opór został wykonany, obrót będzie obsługiwane przez RotateTransform3D więc nie będzie problem.

Problem polega na tym, że nie wiem, jak powinienem radzić sobie z oporem, a dokładniej: Skąd mogę wiedzieć, które ściany sześcianu zostały przeciągnięte, aby określić, która płaszczyzna ma się obracać?

Na przykład w poniższym przypadku chciałbym wiedzieć, że muszę obrócić prawą płaszczyznę sześcianów o 90 stopni zgodnie z ruchem wskazówek zegara, aby rząd niebieskich twarzy był u góry zamiast białych, które będą powrót.

second cube

I w tym przypadku górna warstwa powinna być obracany o 90 stopni przeciwnie do ruchu wskazówek: third cube

Obecnie mój pomysł jest umieszczenie jakiegoś obszarów niewidocznych na kostce, aby sprawdzić, w którym jeden przełomowy drag dzieje się z VisualTreeHelper.HitTest a następnie określić, które należy obrócić samolot, obszar ten będzie pasował do pierwszego przykładu przeciąganie:

enter image description here

Ale kiedy dodaję wszystkie cztery regiony, wracam do kwadratu, ponieważ wciąż muszę określić kierunek i które ściany mają się obracać w zależności od tego, które obszary zostały "dotknięte".

Jestem otwarty na pomysły.

Należy pamiętać, że kostkę można swobodnie przesuwać, więc może nie być w pozycji początkowej, gdy użytkownik klika i przeciąga, to najbardziej przeszkadza mi.

PS: Przeciąganie będzie realizowane przy użyciu kombinacji MouseLeftButtonDown, MouseMove i MouseLeftButtonUp.

+0

Jak utworzyć poszczególne kostki/obszary? Czy każda kostka może mieć własne zdarzenia myszy? – Sphinxxx

+0

@Sphinxxx W tej chwili kostki są 'ModelVisual3D's utworzone z' Geometry3D'. Sądzę więc, że jeśli użyję klasy 'UIElement3D', to tak, mogą mieć własne zdarzenia. Nie widzę jednak, w jaki sposób poszczególne zdarzenia wejściowe mogą pomóc, ponieważ muszę sprawdzić większy obszar (co najmniej półtora kostki), aby upewnić się, że użytkownik chce tego obrotu. – Paul

+0

Zapomniałem wspomnieć w pytaniu, że rotacja powinna działać tak samo, nawet jeśli na przykład teraz mam niebieski rząd i biały rząd na górze. – Paul

Odpowiedz

3

MouseEvents

Musisz użyć VisualTreeHelper.HitTest() odebrać Visual3D obiektów (proces może być prostsze, jeśli każda twarz jest oddzielnym ModelVisual3D). Here to jakaś pomoc na temat HitTestingu w ogóle, a here jest bardzo przydatną ciekawostką, która niezwykle upraszcza proces kompletacji.

Event Culling

Powiedzmy, że masz teraz dwa ModelVisual3D obiekty ze swoich testów kompletacji (jeden z imprezy MouseDown, jeden z imprezy MouseUp). Po pierwsze, powinniśmy wykryć, czy są one współpłaszczyznowe (aby uniknąć wyboru od jednej twarzy do drugiej). Jednym ze sposobów, aby to zrobić, jest porównanie normalnych twarzy, aby zobaczyć, czy wskazują one ten sam kierunek. Jeśli zdefiniowałeś Normalne w MeshGeometry3D, to świetnie. Jeśli nie, to nadal możemy go znaleźć. Sugeruję dodanie klasy statycznej dla rozszerzeń.Przykład obliczania normalny:

public static class GeometricExtensions3D 
{ 
    public static Vector3D FaceNormal(this MeshGeometry3D geo) 
    { 
     // get first triangle's positions 
     var ptA = geo.Positions[geo.TriangleIndices[0]]; 
     var ptB = geo.Positions[geo.TriangleIndices[1]]; 
     var ptC = geo.Positions[geo.TriangleIndices[2]]; 
     // get specific vectors for right-hand normalization 
     var vecAB = ptB - ptA; 
     var vecBC = ptC - ptB; 
     // normal is cross product 
     var normal = Vector3D.CrossProduct(vecAB, vecBC); 
     // unit vector for cleanliness 
     normal.Normalize(); 
     return normal; 
    } 
} 

Stosując tę ​​można porównać normalne na MeshGeometry3D ze swoimi Visual3D trafień (wiele odlew zaangażowanych tutaj) i sprawdzić, czy są skierowane w tym samym kierunku. Użyłbym testu tolerancji na X, Y, Z wektorów w przeciwieństwie do prostej ekwiwalencji, tylko dla bezpieczeństwa. Kolejna rozbudowa może być pomocne:

public static double SSDifference(this Vector3D vectorA, Vector3D vectorB) 
    { 
     // set vectors to length = 1 
     vectorA.Normalize(); 
     vectorB.Normalize(); 
     // subtract to get difference vector 
     var diff = Vector3D.Subtract(vectorA, vectorB); 
     // sum of the squares of the difference (also happens to be difference vector squared) 
     return diff.LengthSquared; 
    } 

Jeśli nie są one w jednej płaszczyźnie (SSDifference> niektóre arbitralna wartość test), można return tutaj (albo dać jakiś rodzaj sprzężenia zwrotnego).

Wybór obiektu

Skoro ustaliliśmy nasze dwie twarze i że są one rzeczywiście dojrzał do naszej pożądanej obsługi zdarzeń, musimy wydedukować sposób Bang informacje z tego, co mamy. Powinieneś wciąż mieć obliczone wcześniej Normalne. Użyjemy ich ponownie, aby wybrać pozostałe twarze do obrócenia. Inną metodą rozszerzenie może być pomocne dla porównania w celu ustalenia, czy twarz powinny być uwzględnione w rotacji:

public static bool SharedColumn(this MeshGeometry3D basis, MeshGeometry3D compareTo, Vector3D normal) 
    { 
     foreach (Point3D basePt in basis.Positions) 
     { 
      foreach (Point3D compPt in compareTo.Positions) 
      { 
       var compToBasis = basePt - compPt; // vector from compare point to basis point 
       if (normal.SSDifference(compToBasis) < float.Epsilon) // at least one will be same direction as 
       {              // as normal if they are shared in a column 
        return true; 
       } 
      } 
     } 
     return false; 
    } 

trzeba zerwać twarze zarówno dla twoich oczek (MouseDown i mouseUp), iteracja ponad wszystko twarze. Zapisz listę geometrii, które należy obrócić.

RotateTransform

Teraz najtrudniejsza część. Obrót osi-kąta przyjmuje dwa parametry: Vector3D reprezentujący oś normalną do obrotu (z regułą prawostronną) i kąt obrotu. Ale punkt środkowy naszego sześcianu może nie być w (0, 0, 0), więc rotacje mogą być trudne. Ergo, najpierw musimy znaleźć punkt środkowy sześcianu! Najprostszy sposób, jaki mogę wymyślić to dodanie elementów X, Y i Z każdego punktu w sześcianie, a następnie podzielenie ich przez liczbę punktów. Sztuczka, oczywiście, nie będzie dodawać tego samego punktu więcej niż raz! Sposób, w jaki to zrobisz, będzie zależeć od tego, jak zorganizowane są twoje dane, ale założę się, że jest to (stosunkowo) trywialne ćwiczenie. Zamiast stosować transformacje, będziesz chciał przenieść punkty same, więc zamiast tworzyć i dodawać do grupy TransformG, będziemy budować Matryce! Macierz tłumaczyć wygląda następująco:

1, 0, 0, dx 
    0, 1, 0, dy 
    0, 0, 1, dz 
    0, 0, 0, 1 

więc, biorąc pod uwagę punkt środkowy swojej kostki, swoje macierze kursowe będą:

var cp = GetCubeCenterPoint(); // user-defined method of retrieving cube's center point 
    // gpu's process matrices in column major order, and they are defined thusly 
    var matToCenter = new Matrix3D(
      1, 0, 0, 0, 
      0, 1, 0, 0, 
      0, 0, 0, 1, 
      -cp.X, -cp.Y, -cp.Z, 1); 
    var matBackToPosition = new Matrix3D(
      1, 0, 0, 0, 
      0, 1, 0, 0, 
      0, 0, 0, 1, 
      cp.X, cp.Y, cp.Z, 1); 

który właśnie opuszcza nasz obrót. Czy nadal masz odniesienia do dwóch siatek, które wybraliśmy z MouseEvents? Dobry! Zdefiniujmy inne rozszerzenie:

public static Point3D CenterPoint(this MeshGeometry3D geo) 
    { 
     var midPt = new Point3D(0, 0, 0); 
     var n = geo.Positions.Count; 
     foreach (Point3D pt in geo.Positions) 
     { 
      midPt.Offset(pt.X, pt.Y, pt.Z); 
     } 
     midPt.X /= n; midPt.Y /= n; midPt.Z /= n; 
     return midPt; 
    } 

Pobierz wektor z MouseDown „s siatki do MouseUp” oczek s (kolejność jest ważna).

var swipeVector = MouseUpMesh.CenterPoint() - MouseDownMesh.CenterPoint(); 

I nadal masz normalne dla naszych hitów, prawda? Możemy (zasadniczo magicznie) dostać oś obrotu przez:

var rotationAxis = Vector3D.CrossProduct(swipeVector, faceNormal); 

co uczyni kąt rotacji zawsze + 90 °.Dokonać RotationMatrix (source):

swipeVector.Normalize(); 
    var cosT = Math.Cos(Math.PI/2); 
    var sinT = Math.Cos(Math.PI/2); 
    var x = swipeVector.X; 
    var y = swipeVector.Y; 
    var z = swipeVector.Z; 
    // build matrix, remember Column-Major 
    var matRotate = new Matrix3D(
     cosT + x*x*(1 -cosT), y*x*(1 -cosT) + z*sinT, z*x*(1 -cosT) -y*sinT, 0, 
     x*y*(1 -cosT) -z*sinT, cosT + y*y*(1 -cosT), y*z*(1 -cosT) -x*sinT, 0, 
     x*z*(1 -cosT) -y*sinT, y*z*(1 -cosT) -x*sinT, cosT + z*z*(1 -cosT), 0, 
          0,      0,      0, 1); 

Połącz je dostać macierz transformacji, trzeba pamiętać, że kolejność jest ważna. Chcemy przyjąć punkt, przekształcić go na współrzędne względem początku, obrócić, a następnie przekształcić z powrotem na oryginalne współrzędne, w tej kolejności. A więc:

var matTrans = Matrix3D.Multiply(Matrix3D.Multiply(matToCenter, matRotate), matBackToPosition); 

Następnie jesteś gotowy, aby przenieść punkty. Iterację każdego Point3D w każdym MeshGeometry3D że wcześniej oznakowanego dla obrotu, a nie:

foreach (MeshGeometry3D geo in taggedGeometries) 
    { 
     for (int i = 0; i < geo.Positions.Count; i++) 
     { 
      geo.Positions[i] *= matTrans; 
     } 
    } 

A potem ... oh czekać, skończymy!

+0

Dziękujemy! Przeczytałem twoją odpowiedź dość szybko, ale mam zamiar zagłębić się w nią jutro, ponieważ teraz jestem zbyt zmęczony. +1 – Paul