2013-01-08 75 views
5

我在WPF中添加了幾個立方體到Viewport3D,現在我想用鼠標操作它們的組。根據鼠標拖動方向和模型中的位置旋轉模型組

initial cube

當我點擊&拖過一個,我想,該阻力被做了方向旋轉孔平面的立方體的一半,旋轉將被RotateTransform3D處理,所以它不會是一個問題。

問題是我不知道如何處理拖動,更確切地說: 如何知道立方體的哪些面被拖動以確定旋轉的平面?

例如,在下面的情況下,我想知道我需要順時針旋轉90度的正方形立方體,這樣一排藍色的面將位於頂部而不是白色的位置,背部。

second cube

而且在這個例子中頂層應逆時針旋轉90度: third cube

目前我的想法是把某種看不見的領域在立方體,以檢查其中一個阻力與​​發生的事情,然後決定我應該旋轉它的平面,這方面將匹配第一個阻力例如:

enter image description here

但是,當我添加所有四個區域,然後我回到原點,因爲我仍然需要根據哪個區域「被觸摸」來確定方向和旋轉哪個面。

我很樂意接受。

請注意,這個立方體可以自由移動,所以當用戶點擊並拖動時它可能不在初始位置,這是最讓我困擾的。

PS: 該拖動將使用MouseLeftButtonDown,MouseMoveMouseLeftButtonUp的組合來實現。

+0

如何創建個別的立方體/區域?每個立方體都可以有自己的鼠標事件嗎? – Sphinxxx

+0

@Sphinxxx目前立方體是'Geometry3D'創建的'ModelVisual3D'。所以我猜如果我使用'UIElement3D'類,那麼是的,他們可以擁有自己的事件。但我沒有看到單個輸入事件如何能夠提供幫助,因爲我需要檢查更廣的區域(至少一個半立方體)以確保用戶需要這種旋轉。 – Paul

+0

我忘了在問題中提到旋轉應該是相同的,例如,現在我有一個藍色的行和一個白色的行。 – Paul

回答

3

MouseEvents

你需要使用VisualTreeHelper.HitTest()Visual3D對象(過程可能是簡單的,如果每個面是一個獨立的ModelVisual3D)。 Here對HitTesting總體上有一些幫助,而here是一個非常有用的珍聞,它極大地簡化了挑選過程。

事件剔除

比方說,你現在有從採摘測試(一個來自MouseDown事件,一個來自MouseUp事件)兩大ModelVisual3D對象。首先,我們應該檢測它們是否共面(以避免選擇從一個面向另一個面)。做到這一點的一種方法是比較面部法線,看他們是否指向相同的方向。如果你在MeshGeometry3D中定義了法線,那太好了。如果不是,那麼我們仍然可以找到它。我建議爲擴展添加一個靜態類。計算一個正常的一個例子:

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; 
    } 
} 

利用這一點,你可以從你Visual3D命中(很多在這裏鑄造參與)比較MeshGeometry3D的法線,看看他們都在指向同一個方向。爲了安全起見,我將對矢量的X,Y,Z使用容差測試,而不是直接等價。另一部分可能會有所幫助:

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; 
    } 

如果他們不共面(SSDifference>一些任意的測試值),你可以在這裏return(或提供某種反饋)。

對象選擇

現在我們已經確定了兩副面孔,他們的確都是成熟的我們所期望的事件處理,我們必須演繹的方式來爆炸了,從我們所掌握的信息。你仍然應該有你之前計算的法線。我們將再次使用它們來挑選要旋轉的其餘面。另一種擴展方法可以幫助進行比較,以確定是否一個面應包括在旋轉:

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; 
    } 

你需要撲殺的面孔爲您的網格(的MouseDown和MouseUp)的,遍歷所有的臉。保存需要旋轉的幾何圖形列表。

RotateTransform

現在棘手的問題。軸角度旋轉有兩個參數:代表與旋轉垂直的軸(使用右手定則)和旋轉角度的Vector3D。但是我們的立方體的中點可能不在(0,0,0),所以旋轉可能會很棘手。人類,首先我們必須找到立方體的中點!我能想到的最簡單的方法是在立方體中添加每個點的X,Y和Z分量,然後將它們除以點數。訣竅當然不會多次添加同一個點!你如何做到這一點將取決於你的數據是如何組織的,但我認爲它是一個(相對)微不足道的練習。您不需要應用變換,而是需要自己移動這些點,所以不是創建並添加到TransformGroup,而是要構建矩陣!一個轉換矩陣的樣子:

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

因此,考慮到您的多維數據集的中點,你的翻譯矩陣將是:

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); 

剛剛離開我們的轉動。你還有沒有參考我們從MouseEvents中選取的兩個網格?好!讓我們來定義另一分機:

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; 
    } 

獲取從MouseDown的絲網到MouseUp的網格(順序很重要)的載體。

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

而且我們的擊球面仍然正常,對嗎?

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

這將使你的旋轉角度總是 + 90°:我們可以(基本上神奇地)通過獲取旋轉軸。使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); 

結合他們得到的轉換矩陣,注意順序很重要。我們想要取點,將其轉換爲相對於原點的座標,旋轉它,然後按照該順序將其轉換回原始座標。所以:

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

然後,你準備好移動點。通過在每個MeshGeometry3D您以前標記爲旋轉每個迭代Point3D,並做到:

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

然後...哦,等等,我們就大功告成了!

+0

謝謝!我很快就讀到了你的答案,但我明天會深入研究,因爲現在我太累了。 +1 – Paul