WPF学习系列 绘制旋转的立方体
目标:
中间的方块会不停的旋转。
第一步,新建wpf项目
第二步,为xaml窗体布局
下面是源代码(不是我写的)
先给grid设置背景颜色: Background="Black"
然后拖一个ContentControl到窗体上,默认的contentcontrol为
删掉这些属性后后,宽高就自动变成100%了。然后将单标签改为双标签。
contentcontrol中间有个Viewport3D标签,在工具箱里找了下,纳尼,没有?这不科学!
网上关于Viewport3D的教程还是很多,但是没有人告诉我他们是怎么拖进去的,只能,手写了。
用WinForm习惯了拖控件,用WPF很不习惯,WPF更倾向于Web的编程布局方式,优势是界面控制性更强,缺点是更难掌握,WPF更适用于对前端样式有更多需求的应用。
ClipToBounds:当设置ClipToBounds为True时,超出部分一定会被裁剪掉;ClipToBounds为False时,超出部分不一定不会被裁剪掉
窗体布局到这里就完了。下面是我的代码,我的原则是感觉没有用的东西就不要写,等它报错。所以很多属性我都删掉了。
第三步 将Viewport3D 属性loaded中的Viewport3D_Loaded写出来
这段代码的作用是绘制viewport3D中的画面。也是这段程序的主体部分
下面是源代码(不是我写的)
先翻译一下
业务逻辑大概就是这样,然后补充相关的代码,让它先能跑起来。
先建一个WpfCube类,
using System.Windows.Media; using System.Windows.Media.Media3D; /// <summary> /// 这个类负责创建一个 立方体的 GeometryModel3D对象 /// </summary> public class WpfCube { private Point3D origin; private double width; private double height; private double depth; public Point3D centerBottom() { Point3D c = new Point3D( origin.X + (width / 2), origin.Y + height, origin.Z + (depth / 2) ); return c; } public Point3D center() { Point3D c = new Point3D( origin.X + (width / 2), origin.Y - height / 2, origin.Z + (depth / 2) ); return c; } public Point3D centerTop() { Point3D c = new Point3D( origin.X + (width / 2), origin.Y, origin.Z + (depth / 2) ); return c; } public WpfCube(Point3D P0, double w, double h, double d) { width = w; height = h; depth = d; origin = P0; } public WpfCube(WpfCube cube) { width = cube.width; height = cube.height; depth = cube.depth; origin = new Point3D(cube.origin.X, cube.origin.Y, cube.origin.Z); } public WpfRectangle Front() { WpfRectangle r = new WpfRectangle(origin, width, height, 0); return r; } public WpfRectangle Back() { WpfRectangle r = new WpfRectangle(new Point3D(origin.X + width, origin.Y, origin.Z + depth), -width, height, 0); return r; } public WpfRectangle Left() { WpfRectangle r = new WpfRectangle(new Point3D(origin.X, origin.Y, origin.Z + depth), 0, height, -depth); return r; } public WpfRectangle Right() { WpfRectangle r = new WpfRectangle(new Point3D(origin.X + width, origin.Y, origin.Z), 0, height, depth); return r; } public WpfRectangle Top() { WpfRectangle r = new WpfRectangle(origin, width, 0, depth); return r; } public WpfRectangle Bottom() { WpfRectangle r = new WpfRectangle(new Point3D(origin.X + width, origin.Y - height, origin.Z), -width, 0, depth); return r; } public static void addCubeToMesh(Point3D p0, double w, double h, double d, MeshGeometry3D mesh) { WpfCube cube = new WpfCube(p0, w, h, d); double maxDimension = Math.Max(d, Math.Max(w, h)); WpfRectangle front = cube.Front(); WpfRectangle back = cube.Back(); WpfRectangle right = cube.Right(); WpfRectangle left = cube.Left(); WpfRectangle top = cube.Top(); WpfRectangle bottom = cube.Bottom(); front.addToMesh(mesh); back.addToMesh(mesh); right.addToMesh(mesh); left.addToMesh(mesh); top.addToMesh(mesh); bottom.addToMesh(mesh); } public GeometryModel3D CreateModel(Color color) { return CreateCubeModel(origin, width, height, depth, color); } public static GeometryModel3D CreateCubeModel(Point3D p0, double w, double h, double d, Color color) { MeshGeometry3D mesh = new MeshGeometry3D(); addCubeToMesh(p0, w, h, d, mesh); Material material = new DiffuseMaterial(new SolidColorBrush(color)); GeometryModel3D model = new GeometryModel3D(mesh, material); return model; } }
然后里面还有一个WpfRectangle类,这个类负责立方体的一个平面
using System.Windows.Media; using System.Windows.Media.Media3D; /// <summary> /// 这个类负责一个构建一个面 /// </summary> public class WpfRectangle { private Point3D p0; private Point3D p1; private Point3D p2; private Point3D p3; /// <summary> /// 必须4个点确定一个长方形 /// </summary> public WpfRectangle(Point3D P0, Point3D P1, Point3D P2, Point3D P3) { p0 = P0; p1 = P1; p2 = P2; p3 = P3; } public WpfRectangle(Point3D P0, double w, double h, double d) { p0 = P0; if (w != 0.0 && h != 0.0) // front / back { p1 = new Point3D(p0.X + w, p0.Y, p0.Z); p2 = new Point3D(p0.X + w, p0.Y - h, p0.Z); p3 = new Point3D(p0.X, p0.Y - h, p0.Z); } else if (w != 0.0 && d != 0.0) // top / bottom { p1 = new Point3D(p0.X, p0.Y, p0.Z + d); p2 = new Point3D(p0.X + w, p0.Y, p0.Z + d); p3 = new Point3D(p0.X + w, p0.Y, p0.Z); } else if (h != 0.0 && d != 0.0) // side / side { p1 = new Point3D(p0.X, p0.Y, p0.Z + d); p2 = new Point3D(p0.X, p0.Y - h, p0.Z + d); p3 = new Point3D(p0.X, p0.Y - h, p0.Z); } } public void addToMesh(MeshGeometry3D mesh) { WpfTriangle.addTriangleToMesh(p0, p1, p2, mesh); WpfTriangle.addTriangleToMesh(p2, p3, p0, mesh); } public static void addRectangleToMesh(Point3D p0, Point3D p1, Point3D p2, Point3D p3, MeshGeometry3D mesh) { WpfTriangle.addTriangleToMesh(p0, p1, p2, mesh); WpfTriangle.addTriangleToMesh(p2, p3, p0, mesh); } public static GeometryModel3D CreateRectangleModel(Point3D p0, Point3D p1, Point3D p2, Point3D p3) { return CreateRectangleModel(p0, p1, p2, p3, false); } public static GeometryModel3D CreateRectangleModel(Point3D p0, Point3D p1, Point3D p2, Point3D p3, bool texture) { MeshGeometry3D mesh = new MeshGeometry3D(); addRectangleToMesh(p0, p1, p2, p3, mesh); Material material = new DiffuseMaterial( new SolidColorBrush(Colors.White)); GeometryModel3D model = new GeometryModel3D(mesh, material); return model; } }
然后我们发现里面还有一个WpfTriangle类 好像是管构造三角形的类,构造四边形用的两个三角形拼凑起的。(为什么要这么麻烦?难道系统没有自带构建四边形的方法?)
using System.Windows.Media; using System.Windows.Media.Media3D; /// <summary> /// 负责构建三角形的类 /// </summary> class WpfTriangle { private Point3D p1; private Point3D p2; private Point3D p3; public WpfTriangle(Point3D P1, Point3D P2, Point3D P3) { p1 = P1; p2 = P2; p3 = P3; } public static void addTriangleToMesh(Point3D p0, Point3D p1, Point3D p2, MeshGeometry3D mesh) { addTriangleToMesh(p0, p1, p2, mesh, false); } public static void addPointCombined(Point3D point, MeshGeometry3D mesh, Vector3D normal) { bool found = false; int i = 0; foreach (Point3D p in mesh.Positions) { if (p.Equals(point)) { found = true; mesh.TriangleIndices.Add(i); mesh.Positions.Add(point); mesh.Normals.Add(normal); break; } i++; } if (!found) { mesh.Positions.Add(point); mesh.TriangleIndices.Add(mesh.TriangleIndices.Count); mesh.Normals.Add(normal); } } public static void addTriangleToMesh(Point3D p0, Point3D p1, Point3D p2, MeshGeometry3D mesh, bool combine_vertices) { Vector3D normal = CalculateNormal(p0, p1, p2); if (combine_vertices) { addPointCombined(p0, mesh, normal); addPointCombined(p1, mesh, normal); addPointCombined(p2, mesh, normal); } else { mesh.Positions.Add(p0); mesh.Positions.Add(p1); mesh.Positions.Add(p2); mesh.TriangleIndices.Add(mesh.TriangleIndices.Count); mesh.TriangleIndices.Add(mesh.TriangleIndices.Count); mesh.TriangleIndices.Add(mesh.TriangleIndices.Count); mesh.Normals.Add(normal); mesh.Normals.Add(normal); mesh.Normals.Add(normal); } } public GeometryModel3D CreateTriangleModel(Color color) { return CreateTriangleModel(p1, p2, p3, color); } public static GeometryModel3D CreateTriangleModel(Point3D P0, Point3D P1, Point3D P2, Color color) { MeshGeometry3D mesh = new MeshGeometry3D(); addTriangleToMesh(P0, P1, P2, mesh); Material material = new DiffuseMaterial(new SolidColorBrush(color)); GeometryModel3D model = new GeometryModel3D(mesh, material); return model; } public static Vector3D CalculateNormal(Point3D P0, Point3D P1, Point3D P2) { Vector3D v0 = new Vector3D(P1.X - P0.X, P1.Y - P0.Y, P1.Z - P0.Z); Vector3D v1 = new Vector3D(P2.X - P1.X, P2.Y - P1.Y, P2.Z - P1.Z); return Vector3D.CrossProduct(v0, v1); } }
然后看看源代码
我所有文件都建好了,只剩完善 MainWindow.xaml.cs中的代码了。好开心
下面重点研究下下面的方法
WpfCube cube = new WpfCube(new System.Windows.Media.Media3D.Point3D(0, 0, 0), sceneSize / 6, sceneSize / 6, sceneSize / 6);
这行代码调用wpfcube类的构造函数,定义原点,宽高深(我还是喜欢坐标表示法中的,原点,x轴,y轴,z轴)
接着创建一个模型出来,传入模型的颜色,然后这个方法又调用绘制6个面的方法
GeometryModel3D cubeModel = cube.CreateModel(Colors.Aquamarine);
模型有了,然后要把他放到场景中去:
//创建一个模型集合来保存我们的模型 Model3DGroup groupScene = new Model3DGroup(); //将我们的模型添加到模型集合 groupScene.Children.Add(cubeModel);
然后需要添加光线,不然就是漆黑一片,将源码中的添加光线注释掉,然后将Grid的背景颜色设为蓝色显示如下。
创建模型的时候,我们选了颜色Colors.Aquamarine,但是显示出来是黑色就是因为没有光。
所以要正常显示还要往场景中加入光
// 添加一个方向光 groupScene.Children.Add(positionLight(new Point3D(-sceneSize, sceneSize / 2, 0.0))); // 添加环境光 groupScene.Children.Add(new AmbientLight(Colors.Gray));
public DirectionalLight positionLight(Point3D position) { DirectionalLight directionalLight = new DirectionalLight(); directionalLight.Color = Colors.Gray; directionalLight.Direction = new Point3D(0, 0, 0) - position; return directionalLight; }
仔细研究了下这个方法发现这个方法是传入一个点然后返回从原点到这个点方向的灰色光线,应该是做阴影效果的。
一个环境光一个阴影光,就将立方体衬托出来了。
为viewport3D设置视觉模型
#region 得到视觉模型 visual // 创建一个视觉模型--我们眼睛看到的 ModelVisual3D visual = new ModelVisual3D(); // 用我们做的几何模型来填充视觉模型 visual.Content = groupScene; #endregion // 将视觉模型visual添加到viewport3D窗体中 viewport.Children.Add(visual);
这样之就将模型场景封装成一个视觉模型,添加到viewport3D中,但是现在还看不见。
需要为viewport3D设置Camera,因为屏幕上只是个平面,我们的模型是3D的,所以要设置一个观察点,将3D模型的投影成一个平面图形。
viewport.Camera = camera(); /// <summary> /// 得到一个透视投影摄像机 /// </summary> /// <returns></returns> public PerspectiveCamera camera() { PerspectiveCamera perspectiveCamera = new PerspectiveCamera(); perspectiveCamera.Position = new Point3D(-sceneSize, sceneSize / 2, sceneSize);//设置观察点 perspectiveCamera.LookDirection = new Vector3D(lookat.X - perspectiveCamera.Position.X, lookat.Y - perspectiveCamera.Position.Y, lookat.Z - perspectiveCamera.Position.Z);//设置观察方向 空间向量的表示方式 perspectiveCamera.FieldOfView = 60;//水平视角 return perspectiveCamera; } Point3D lookat = new Point3D(0, 0, 0);
现在的效果如下:
最后就是让模型动起来
turnModel(cube.center(), cubeModel, 0, 360, 3, true); /// <summary> /// 模型动起来 /// </summary> /// <param name="center"></param> /// <param name="model"></param> /// <param name="beginAngle">开始角度</param> /// <param name="endAngle">结束角度</param> /// <param name="seconds"></param> /// <param name="forever"></param> public void turnModel(Point3D center, GeometryModel3D model, double beginAngle, double endAngle, double seconds, bool forever) { //向量作为两个旋转轴 Vector3D vector = new Vector3D(0, 1, 0); Vector3D vector2 = new Vector3D(1, 0, 0); // 创建AxisAngleRotation3D(三维旋转)。我们可以设置一个0度的旋转,因为我们要给他们做动画 AxisAngleRotation3D rotation = new AxisAngleRotation3D(vector, 0.0); AxisAngleRotation3D rotation2 = new AxisAngleRotation3D(vector2, 0.0); // 创建双动画来动画我们的每一个旋转 DoubleAnimation doubleAnimation = new DoubleAnimation(beginAngle, endAngle, durationTS(seconds)); DoubleAnimation doubleAnimation2 = new DoubleAnimation(beginAngle, endAngle, durationTS(seconds)); // 为我们的动画设置重复的行为和持续时间 if (forever) { doubleAnimation.RepeatBehavior = RepeatBehavior.Forever; doubleAnimation2.RepeatBehavior = RepeatBehavior.Forever; } doubleAnimation.BeginTime = durationTS(0.0); doubleAnimation2.BeginTime = durationTS(0.0); // 创建2个旋转变换应用到我们的模型。每个需要一个旋转和一个中心点 RotateTransform3D rotateTransform = new RotateTransform3D(rotation, center); RotateTransform3D rotateTransform2 = new RotateTransform3D(rotation2, center); // 创建一个转换组来保持我们的2个转换 Transform3DGroup transformGroup = new Transform3DGroup(); transformGroup.Children.Add(rotateTransform); transformGroup.Children.Add(rotateTransform2); // 将旋转组添加到模型上 model.Transform = transformGroup; // 开始动画 -- specify a target object and property for each animation -- in this case, //目标是我们创造的两个AxisAngleRotation3D对象 并且 我们改变每个的角度属性 rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, doubleAnimation); rotation2.BeginAnimation(AxisAngleRotation3D.AngleProperty, doubleAnimation2); } private int durationM(double seconds) { int milliseconds = (int)(seconds * 1000); return milliseconds; } public TimeSpan durationTS(double seconds) { TimeSpan ts = new TimeSpan(0, 0, 0, 0, durationM(seconds)); return ts; }
然后就可以动了。
但是先在做模型都是用的unity3d和cocos2d程序里面直接写模型就只能在cpu中跑,gpu就不管了
----------------------分割线 2016-8-4 15:40----------------------
今天,终于知道viewport3d控件在哪里了。
工具栏中开始是不显示的,要调出来。
步骤如下:
1、打开工具箱窗口 (视图-工具箱)
2、在工具箱面板中点击右键、选择 选择项 ,就可以看到下面的视图。然后选择自己要的添加进来就行了。