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!
Jak utworzyć poszczególne kostki/obszary? Czy każda kostka może mieć własne zdarzenia myszy? – Sphinxxx
@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
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