6

WPFのにいくつかのキューブを追加しましViewport3Dた。次に、それらのグループをマウスで操作したいと思います。

初期キューブ

それらの立方体の1.5個をクリックしてドラッグすると、ドラッグが行われた方向に穴の平面を回転させたいので、回転はによって処理されるRotateTransform3Dので、問題はありません。

問題は、ドラッグをどのように処理する必要があるかわからないことです。より正確には、回転する平面を決定するために、立方体のどの面がドラッグされたかをどのように知ることができますか?

たとえば、以下の場合、立方体の右側の平面を時計回りに90度回転させて、後ろにある白い面ではなく、青い面の列が上になるようにする必要があることを知りたいと思います。

2番目のキューブ

この例では、最上層を反時計回りに90度回転させる必要があります。 3番目のキューブ

現在の私の考えは、立方体の上にある種の非表示領域を配置し、ドラッグが発生している領域を確認してから、VisualTreeHelper.HitTest回転する平面を決定することです。この領域は、最初のドラッグの例と一致します。

ここに画像の説明を入力してください

しかし、4つの領域すべてを追加すると、「タッチ」された領域に応じて回転する方向と面を決定する必要があるため、正方形の1つに戻ります。

私はアイデアを受け入れています。

この立方体は自由に動かすことができるので、ユーザーがクリックしてドラッグしたときに初期位置にない可能性があることに注意してください。これが私にとって最も厄介なことです。

MouseLeftButtonDownPS:ドラッグは、MouseMoveとの組み合わせで実装されMouseLeftButtonUpます。

4

1 に答える 1

5

MouseEvents

VisualTreeHelper.HitTest()オブジェクトを選択するためにを使用する必要がありVisual3Dます(各面が別々の場合、プロセスはより簡単になる場合がありますModelVisual3D)。ここでは、HitTesting全般に関するヘルプをいくつか紹介します。ここでは、ピッキングプロセスを大幅に簡素化する非常に便利な情報を紹介します。

イベントカリング

ModelVisual3Dこれで、ピッキングテストからの2つのオブジェクト(1つはイベントMouseDownから、もう1つはMouseUpイベントから)があるとします。まず、それらが同一平面上にあるかどうかを検出する必要があります(ピックが1つの面から別の面に移動するのを防ぐため)。これを行う1つの方法は、面の法線を比較して、それらが同じ方向を指しているかどうかを確認することです。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;
    }
}   

MeshGeometry3Dこれを使用して、ヒットからの法線Visual3D(ここでは多くのキャストが含まれます)を比較し、それらが同じ方向を指しているかどうかを確認できます。安全のために、真っ直ぐな等価ではなく、ベクトルの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ここで(または何らかのフィードバックを提供する)ことができます。

オブジェクトの選択

2つの面を決定し、それらが実際に目的のイベント処理に熟しているので、私たちが持っているものから情報を引き出す方法を推測する必要があります。以前に計算した法線がまだ残っているはずです。それらを再度使用して、回転する残りの面を選択します。別の拡張方法は、顔を回転に含める必要があるかどうかを判断するための比較に役立ちます。

    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

今トリッキーな部分。軸-角度回転は、2つのパラメーターを取ります。aVector3Dは回転に垂直な軸を表し(右手の法則を使用)、回転角を表します。ただし、立方体の中点は(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から選択した2つのメッシュへの参照はまだありますか?良い!別の拡張機能を定義しましょう:

    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'sメッシュから'sメッシュへのベクトルを取得しますMouseUp(順序は重要です)。

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

そして、あなたはまだ私たちのヒットフェイスの正常性を持っていますよね?(基本的に魔法のように)次の方法で回転軸を取得できます。

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

これにより、回転角は常に+90°になります。RotationMatrix(ソース)を作成します。

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

これで、ポイントを移動する準備が整いました。以前にローテーションのタグを付けたものをそれぞれ繰り返して、次のようにしPoint3Dます。MeshGeometry3D

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

そして...ああ、待って、完了です!

于 2013-01-08T20:48:46.930 に答える