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;
}
}
そして...ああ、待って、完了です!