Pau*_*aul 5 c# 3d wpf graphics
我Viewport3D
在WPF中添加了几个立方体,现在我想用鼠标操作它们的组.
当我点击并拖动这些立方体中的一半时,我希望孔平面在拖动方向上旋转,旋转将被处理,RotateTransform3D
因此它不会成为问题.
问题是我不知道应该如何处理阻力,更确切地说:我如何知道立方体的哪些面被拖过以确定要旋转的平面?
例如,在下面的情况下,我想知道我需要顺时针旋转立方体的右平面,因此蓝色面的行将位于顶部,而不是位于背面的白色面.
在此示例中,顶层应逆时针旋转90度:
目前我的想法是在立方体上放置某种不可见的区域,以检查拖动发生在哪一个VisualTreeHelper.HitTest
,然后确定我应该旋转哪个平面,该区域将匹配第一个拖动示例:
但是当我添加所有四个区域时,我又回到原点,因为我仍然需要根据哪些区域"触摸"确定方向和旋转面.
我愿意接受各种想法.
请注意,这个立方体可以自由移动,因此当用户点击和拖动时它可能不在初始位置,这最让我困扰的是.
PS:拖动将的组合来实现MouseLeftButtonDown
,MouseMove
和MouseLeftButtonUp
.
小智 5
鼠标事件
您需要使用VisualTreeHelper.HitTest()
来选择Visual3D
对象(如果每个面都是单独的,过程可能会更简单ModelVisual3D
)。这里有一些关于 HitTesting 的一般帮助,这里有一个非常有用的花絮,它极大地简化了挑选过程。
事件剔除
假设您现在有两个ModelVisual3D
来自挑选测试的对象(一个来自MouseDown
事件,一个来自MouseUp
事件)。首先,我们应该检测它们是否共面(以避免选择从一个面到另一个面)。一种方法是比较面法线以查看它们是否指向相同的方向。如果您已经在 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;
}
}
Run Code Online (Sandbox Code Playgroud)
使用这个,你可以比较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;
}
Run Code Online (Sandbox Code Playgroud)
如果它们不共面(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;
}
Run Code Online (Sandbox Code Playgroud)
您需要为您的两个网格(MouseDown 和 MouseUp)剔除面,迭代所有面。保存需要旋转的几何体列表。
旋转变换
现在是棘手的部分。Axis-Angle 旋转有两个参数:aVector3D
表示垂直于旋转的轴(使用右手定则)和旋转角度。但是立方体的中点可能不在 (0, 0, 0) 处,因此旋转可能会很棘手。因此,首先我们必须找到立方体的中点!我能想到的最简单的方法是将立方体中每个点的 X、Y 和 Z 分量相加,然后将它们除以点数。当然,诀窍是不要多次添加相同的点!你如何做到这一点取决于你的数据是如何组织的,但我认为这是一个(相对)微不足道的练习。您不想应用变换,而是希望自己移动点,因此我们将构建矩阵,而不是创建和添加到 TransformGroup!翻译矩阵如下所示:
1, 0, 0, dx
0, 1, 0, dy
0, 0, 1, dz
0, 0, 0, 1
Run Code Online (Sandbox Code Playgroud)
因此,给定多维数据集的中点,您的翻译矩阵将是:
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);
Run Code Online (Sandbox Code Playgroud)
这只是离开我们的轮换。你还有参考我们从 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;
}
Run Code Online (Sandbox Code Playgroud)
获取从MouseDown
的网格到 的网格的向量MouseUp
(顺序很重要)。
var swipeVector = MouseUpMesh.CenterPoint() - MouseDownMesh.CenterPoint();
Run Code Online (Sandbox Code Playgroud)
你仍然有我们命中的脸的法线,对吧?我们可以(基本上神奇地)通过以下方式获得旋转轴:
var rotationAxis = Vector3D.CrossProduct(swipeVector, faceNormal);
Run Code Online (Sandbox Code Playgroud)
这将使您的旋转角度始终为+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);
Run Code Online (Sandbox Code Playgroud)
将它们组合起来得到变换矩阵,注意顺序很重要。我们想要取点,将其转换为相对于原点的坐标,旋转它,然后将其转换回原始坐标,按此顺序。所以:
var matTrans = Matrix3D.Multiply(Matrix3D.Multiply(matToCenter, matRotate), matBackToPosition);
Run Code Online (Sandbox Code Playgroud)
然后,您就可以移动点了。遍历您之前标记为旋转的每个Point3D
中的每个MeshGeometry3D
,然后执行:
foreach (MeshGeometry3D geo in taggedGeometries)
{
for (int i = 0; i < geo.Positions.Count; i++)
{
geo.Positions[i] *= matTrans;
}
}
Run Code Online (Sandbox Code Playgroud)
然后……哦等等,我们完成了!
归档时间: |
|
查看次数: |
1696 次 |
最近记录: |