实现WPF 3D中拖拽图形的两种方法

  在网上看到别人实现的拖拽鼠标旋转三维图形的方法,节选如下(原文地址):http://www.codeproject.com/Articles/23332/WPF-3D-Primer 。

The difficult part of this task is to map a 2D vector (the mouse movement) to a 3D rotation of the solid. It took me a while to figure it out, but the mouse movement vector can be easily converted to a rotation angle, applied to the solid around a rotation axis that is coplanar with the screen and perpendicular to the mouse movement vector, as shown in the figure below.

Mouse.png 

The rotation axis is pinned in the origin (0,0,0) because the solid is centered in the origin itself, and both the mouse movement vector and the rotation axis have the Z coordinate set to zero, so they are coplanar with the XY plane.

The rotation axis has a direction, indicated by the arrow in the previous figure. Angles are always calculated in clockwise direction, so if you rotate the solid with a positive angle, it will rotate leftwards:

Rotation.png 

In order to properly calculate the rotation axis, we first need to calculate the angle of the mouse movement vector. Basic trigonometry tells us that the angle, alpha, is computed as follows:

Trigonometry.png 


(省略按下和弹起部分)

The MouseMove handler has a lot of work to do. See the complete code:

private void Grid_MouseMove(object sender, MouseEventArgs e) {

    if(!mDown) return;

    Point pos = Mouse.GetPosition(viewport);

    Point actualPos = new Point(

            pos.X – viewport.ActualWidth / 2,

            viewport.ActualHeight / 2 – pos.Y);

    double dx = actualPos.X – mLastPos.X;

    double dy = actualPos.Y – mLastPos.Y;

    double mouseAngle = 0;


    if(dx != 0 && dy != 0) {

        mouseAngle = Math.Asin(Math.Abs(dy) /

            Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)));

        if(dx < 0 && dy > 0) mouseAngle += Math.PI / 2;

        else if(dx < 0 && dy < 0) mouseAngle += Math.PI;

        else if(dx > 0 && dy < 0) mouseAngle += Math.PI * 1.5;

    }

    else if(dx == 0 && dy != 0) {

            mouseAngle = Math.Sign(dy) > 0 ? Math.PI / 2 : Math.PI * 1.5;

    }

    else if(dx != 0 && dy == 0) {

            mouseAngle = Math.Sign(dx) > 0 ? 0 : Math.PI;

    }


    double axisAngle = mouseAngle + Math.PI / 2;


    Vector3D axis = new Vector3D(

            Math.Cos(axisAngle) * 4,

            Math.Sin(axisAngle) * 4, 0);


    double rotation = 0.02 *

            Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));


    Transform3DGroup group = mGeometry.Transform as Transform3DGroup;

       QuaternionRotation3D r =

            new QuaternionRotation3D(

            new Quaternion(axis, rotation * 180 / Math.PI));

    group.Children.Add(new RotateTransform3D(r));


    mLastPos = actualPos;

}


The method first checks if the mouse left button is pressed; if not, it exists. The mouse dx and dy values are then calculated using the previous position stored in mLastPos, converting the current position to the 3D coordinate system as shown for the Grid_MouseDown event handler.

The angle of the mouse movement vector is then calculated with the formula we saw before, with three different cases:

  • If dx and dy are both different from zero, mouseAngle is computed using the absolute value of dy, and then it’s corrected according to the sign of both dx and dy.

  • If dx is zero, mouseAngle must be either 90 or 270 degrees (we use the sign of dy).

  • If dy is zero, mouseAngle must be either 0 or 180 degrees (we use the sign of dx).

The angle of the rotation axis (axisAngle) is calculated adding 90 degrees to the mouse movement vector angle. Keep in mind that these angles are always referred to the X axis.

RotationAxes.png 

The only thing we have to do now is to apply a transformation to the geometry. Since we want to rotate the solid (but it’s possible to do many other things), we have to add an instance of RotateTransform3D to theTransform3DGroup collection we stored in the Transform property of the geometry (have a look at theBuildSolid method). We cast the collection and store it in the group variable.

The constructor of RotateTransform3D needs an instance of a class inheriting from Rotation3D describing the rotation to apply (see MSDN). Since we want to apply a rotation around an axis and by a given angle, we useQuaternionRotation3D and we instantiate it with axis and the rotation angle (in degrees, not in radians).

So far I omitted a detail. The rotation axis is not sufficient to define the actual axis of the rotation, because it also needs a center which is, in our case, the origin. The constructor of RotateTransform3D accepts an optional parameter that allows to specify the center of the rotation. If omitted, the origin is used.

(省略其余部分)

  其实,如果通过改变当前Camera的位置和方向会更加简便。

  设观测位置在一个特定半径的球面上移动,屏幕上的内容实际是视图中的物体在与球面相切的平面上的投影,如下图所示。

  以鼠标拖拽的形式进行视角的切换,鼠标向某一位置拖拽时,使切点沿着该位置向球面的其他位置移动。

  将当前摄像机的位置用三元式(R,φ,θ)表示,各分量对应球面参数方程(参见3.2.1节)中的参数。设用户按下和拖拽鼠标时,鼠标在屏幕上改变的坐标为(dx,dy),则与球面参数有对应式:

  式中120的比例因子是实际试验得出的较合适的值,比例因子越大,对摄像机坐标的改变幅度越小。每次获取到鼠标拖拽时的新坐标值时,设定摄像机新的位置(R,φ*,θ*),即可实现视角的转换。

  部分代如下:

  SetCameraPosition方法用于设置摄像机位置。表示摄像机当前在半径为R,经纬度分别问CameraTheta和CameraPhi的球面上,代码如下。

  当然这是两种不同的实现方式,一种是旋转图形,另一种则是改变摄像机位置。如果不想改变光源和摄像机,那么就用第一种。但第二种更利于理解。

3条评论


  1. 我看着也像VB,只是…..VB现在还在用于开发商业产品? 这方面我还是很孤陋寡闻的

    回复

发表评论

电子邮件地址不会被公开。