部分源码参考Github,是一本名为WPF-3D的书的代码,有条件的可以买下实体书。
本文源码地址:WPF3D+鼠标控制相机视角+封装成类
将键盘控制封装成类
目前已经实现了通过上下左右和Q、E这六个键来调整相机的视角,由于这些功能彼此之间比较集中,按照低耦合、高内聚的原则,封装成一个类是十分合理的。
而这样做的结果就是,让MainWindow
中的代码大大缩减,只剩下Window_Loaded
、DefineCamera
、DefineLights
、DefineModel
、MakeCubeMesh
这5个自定义的函数。而原本的KeyboardControl_KeyDown
等功能就要移到相机控制类了。
最终得到的效果与未封装成类时是完全一样的
其中,与相机相关的函数、成员变量为
private PerspectiveCamera TheCamera = null;
// 相机遥控器,CameraController是自定义了一个类
private CameraController cc = null;
private void DefineCamera(Viewport3D viewport)
{
TheCamera = new PerspectiveCamera();
TheCamera.FieldOfView = 60;
cc = new CameraController(TheCamera, viewport, this);
}
接下来就要写CameraController
这个类,其成员变量如下
// 每次转换的的最小值
public const double cmDR = 0.1;
public const double cmDTheta = Math.PI / 30;
//相机
public PerspectiveCamera cm = null;
// 传入主窗口的动作
private UIElement mainWindow = null;
// 相机位置和方向
public Point3D cmPosition { get; set; } = new Point3D(4, 0.5, 5);
public double cmTheta = Math.PI * 1.3;
其中,两个常量cmDR
和cmDThera
分别表示单次移动的距离和角度。例如按一下左箭头,那么当前相机的角度就减少cmDTheta
。
构造函数为
public CameraController(PerspectiveCamera camera, Viewport3D viewport,UIElement mainWindow)
{
cm = camera;
viewport.Camera = cm;
this.mainWindow = mainWindow;
this.mainWindow.PreviewKeyDown += mainWindow_KeyDown;
PositionCamera();
}
最后是与按键的交互逻辑,这些内容是上一节已经写过的,就不再详述了
// 将角度转为向量
protected Vector3D AngleToVector(double angle, double length)
{
return new Vector3D(
length * Math.Cos(angle), 0, length * Math.Sin(angle));
}
protected void MoveLR(bool isLeft=true)
{
Vector3D v = AngleToVector(cmTheta, cmDR);
if (isLeft)
cmPosition += new Vector3D(v.Z, 0, -v.X);
else
cmPosition += new Vector3D(-v.Z, 0, v.X);
}
//向上或者向下移动
protected void MoveUD(bool isUp = true)
{
Vector3D v = AngleToVector(cmTheta, cmDR);
if (isUp)
cmPosition += v;
else
cmPosition -= v;
}
// 其中 上、下、Q、E代表平移
// 左右代表旋转
private void mainWindow_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Left: cmTheta -= cmDTheta;break;
case Key.Right: cmTheta += cmDTheta;break;
case Key.Up:MoveUD(true);break;
case Key.Down: MoveUD(false);break;
case Key.Q: MoveLR(true);break;
case Key.E: MoveLR(false);break;
}
// 更新相机位置
PositionCamera();
}
// 更新相机的位置
protected virtual void PositionCamera()
{
cm.Position = cmPosition;
cm.LookDirection = AngleToVector(cmTheta, 1);
cm.UpDirection = new Vector3D(0, 1, 0);
}
用鼠标控制视角
仅从代码的结构来说,用鼠标控制和用键盘控制没有任何区别,区别仅在于二者的返回值是不同的。
所以初始化代码是高度相似的
public CameraController(PerspectiveCamera camera, Viewport3D viewport,
UIElement mainWindow)
{
cm = camera;
viewport.Camera = cm;
this.mainWindow = mainWindow;
//鼠标按下时的动作
this.mainWindow.MouseLeftButtonDown += mainWindow_LeftDown;
PositionCameraMouse();
}
最终得到的结果为
其中mainWindow_LeftDown
就是完成动作的关键,顾名思义,这个函数的触发条件是按下鼠标左键。而其实现的功能则为,在左键已经按下的情况下,拖动鼠标,可实现相机视角的变化,所以这个函数写为
private Point ptLast;
private void mainWindow_LeftDown(object sender, MouseButtonEventArgs e)
{
mainWindow.CaptureMouse();
mainWindow.MouseMove += MainWindow_MouseMove;
mainWindow.MouseUp += MainWindow_MouseUp;
ptLast = e.GetPosition(mainWindow);
}
其中,ptLast
是一个全局变量,用于保存鼠标按下时的位置。MainWindow_MouseMove
是鼠标移动时执行的动作,MainWindow_MouseUp
是鼠标离开时的动作,显然后者更容易实现,而且这种鼠标点击时绑定事件、鼠标松开时解绑事件也是一种非常通用的写法,应用十分广泛,具体写法如下:
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
mainWindow.ReleaseMouseCapture();
mainWindow.MouseMove -= MainWindow_MouseMove;
mainWindow.MouseUp -= MainWindow_MouseUp;
}
MouseMove
写的就是交互功能了,
private void MainWindow_MouseMove(object sender, MouseEventArgs e)
{
const double xscale = 0.1;
const double yscale = 0.1;
Point newPoint = e.GetPosition(mainWindow);
double dx = newPoint.X - ptLast.X;
double dy = newPoint.Y - ptLast.Y;
CameraTheta -= dx * CameraDTheta * xscale;
CameraPhi -= dy * CameraDPhi * yscale;
ptLast = newPoint;
PositionCameraMouse();
}
private void PositionCameraMouse()
{
double x, y, z;
y = CameraR * Math.Cos(CameraPhi);
double h = CameraR * Math.Sin(CameraPhi);
x = h * Math.Sin(CameraTheta);
z = h * Math.Cos(CameraTheta);
cm.Position = new Point3D(x, y, z);
cm.LookDirection = new Vector3D(-x, -y, -z);
cm.UpDirection = new Vector3D(0, 1, 0);
}
WPF 3D目录
3D开发主要有两条线索,分别是三维实体的生成和相机视角的转换。前者要求理解计算机中几何图形的数据组织形式,后者要求理解相机模型,通晓三维形体在二维屏幕上的映射过程。相应地,通过鼠标、键盘和3D场景的交互,也分别从这两个方面出发,即一方面控制相机的视角,实现场景的变换,另一方面控制形体的位置,实现对目标的操作。