WPF 步行机器人系列 -- 第 2 部分:圆形、圆柱体、平面和光滑着色






4.50/5 (4投票s)
关于如何使用 C# 代码在 WPF 中制作动画 3D 机器人的系列第二部分

引言
这是步行机器人系列的第 2 部分,我们将使用 WPF 中的 C# 代码创建一个简单的动画机器人角色。
本文包含了所有源代码。 该项目可以在 Visual Studio 2010 中构建。
让我们使用我们在上一篇文章中定义的 WpfCube
类来创建一个简单的环境。
double floorThickness = WpfScene.sceneSize / 100;
GeometryModel3D floorModel = WpfCube.CreateCubeModel(
new Point3D(-WpfScene.sceneSize / 2,
-floorThickness,
-WpfScene.sceneSize / 2),
WpfScene.sceneSize, floorThickness, WpfScene.sceneSize, Colors.Tan);
Model3DGroup groupScene = new Model3DGroup();
groupScene.Children.Add(floorModel);
我们在加载 Viewport3D
时调用此代码,使用我们之前定义的事件。 这将为我们的机器人创建一个行走的地面。 在本文中,我们将只定义一些我们需要制作机器人的形状。
我们将需要一个 circle
类。 以下是我们构建 circle
所需的一些代码
private int nSides = 6;
private Point3D center;
private List<Point3D> points;
private double radiusY;
private double radiusX;
public WpfCircle(int NSides, Point3D Center, double Radius)
{
nSides = NSides;
angle = (double)360.0 / (double)nSides;
center = new Point3D(Center.X, Center.Y, Center.Z);
radiusY = Radius;
radiusX = Radius;
makeCircle();
}
public WpfCircle(int NSides, Point3D Center, double RadiusY, double RadiusX)
{
nSides = NSides;
angle = (double)360.0 / (double)nSides;
center = new Point3D(Center.X, Center.Y, Center.Z);
radiusY = RadiusY;
radiusX = RadiusX;
makeCircle();
}
private void makeCircle()
{
points = new List<Point3D>();
top = new Point3D(center.X, center.Y + radiusY, center.Z);
points.Add(top);
for (int i = 1; i < nSides; i++)
{
Point3D p = WpfUtils.RotatePointXY
(top, center, WpfUtils.radians_from_degrees(angle * i));
if (radiusX != radiusY)
{
double diff = p.X - center.X;
diff *= radiusX;
diff /= radiusY;
p = new Point3D(center.X + diff, p.Y, p.Z);
}
points.Add(p);
}
}
只需要一个中心点以及 X 和 Y 半径即可构建我们的圆形。 和以前一样,我们需要一种将圆形的三角形添加到几何网格的方法
public void addToMesh(MeshGeometry3D mesh, bool combineVertices)
{
if (points.Count > 2)
{
List<Point3D> temp = new List<Point3D>();
foreach (Point3D p in points)
{
temp.Add(p);
}
temp.Add(points[0]);
for (int i = 1; i < temp.Count; i++)
{
WpfTriangle.addTriangleToMesh(temp[i], center, temp[i - 1],
mesh, combineVertices);
}
}
}
我们提供从 circle
对象创建 GeometryModel3D
的方法。 为了说明 WPF 的工作原理,这里有两种版本的此方法
public GeometryModel3D createModel(Color color, bool combineVertices)
{
MeshGeometry3D mesh = new MeshGeometry3D();
addToMesh(mesh, combineVertices);
Material material = new DiffuseMaterial(
new SolidColorBrush(color));
GeometryModel3D model = new GeometryModel3D(mesh, material);
return model;
}
public GeometryModel3D createModelTwoSided(Color color, bool combineVertices)
{
MeshGeometry3D mesh = new MeshGeometry3D();
addToMesh(mesh, combineVertices);
Material material = new DiffuseMaterial(
new SolidColorBrush(color));
GeometryModel3D model = new GeometryModel3D(mesh, material);
model.BackMaterial = material;
return model;
}
createModel
方法的一个版本创建一个双面模型。 唯一的区别是这行代码
model.BackMaterial = material;
如果运行演示,您将看到创建的两个圆形在旋转。 其中一个圆圈从两面都可见,因为它设置了 BackMaterial
。 另一个只能从一面看到。
我们不会将任何圆形用于我们的机器人,但是我们需要圆形,以便我们可以构建将用于机器人的圆柱形状。 这是我们的 cylinder
类的开头
private WpfCircle front;
private WpfCircle back;
private int nSides;
private double frontRadius;
private double backRadius;
private double length;
private Point3D center;
public Cylinder(Point3D Center, int NSides, double FrontRadius, double BackRadius,
double Length)
{
center = Center;
nSides = NSides;
frontRadius = FrontRadius;
backRadius = BackRadius;
length = Length;
front = new WpfCircle(nSides, center, frontRadius);
backcenter = new Point3D(center.X, center.Y, center.Z - length);
back = new WpfCircle(nSides, backcenter, backRadius);
}
正如你所看到的,我们使用两个圆来构建我们的 cylinder
。 这是将三角形添加到此形状的网格的方法。 它添加了侧面的三角形,然后让每个 circle
对象将其自己的三角形添加到网格
public void addToMesh(MeshGeometry3D mesh, bool encloseTop, bool combineVertices)
{
if (front.getPoints().Count > 2)
{
List<Point3D> frontPoints = new List<Point3D>();
foreach (Point3D p in front.getPoints())
{
frontPoints.Add(p);
}
frontPoints.Add(front.getPoints()[0]);
List<Point3D> backPoints = new List<Point3D>();
foreach (Point3D p in back.getPoints())
{
backPoints.Add(p);
}
backPoints.Add(back.getPoints()[0]);
for (int i = 1; i < frontPoints.Count; i++)
{
WpfTriangle.addTriangleToMesh(frontPoints[i - 1],
backPoints[i - 1], frontPoints[i], mesh, combineVertices);
WpfTriangle.addTriangleToMesh(frontPoints[i],
backPoints[i - 1], backPoints[i], mesh, combineVertices);
}
if (encloseTop)
{
front.addToMesh(mesh, false);
back.addToMesh(mesh, false);
}
}
}
请注意,我们有一个 combineVertices
参数。 在机器人系列的第一篇文章中,我们讨论了如何通过组合顶点使您的模型具有光滑着色。 我们不希望我们的立方体具有光滑着色,但我们确实希望我们的圆柱体具有光滑着色。 请注意,在将三角形添加到圆柱体的侧面时,我们使用了我们在第一篇文章中定义的 WpfTriangle.addTriangleToMesh
方法。 它使用我们传入的 combineVertices
参数调用该方法,因此我们可以为我们的三角形指定光滑或平面着色。 请注意,在将我们的前圆和后圆添加到网格时,我们始终传入 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);
}
}
您可以通过运行演示看到这种效果。 除了两个旋转的圆圈外,我们还有两个旋转的圆柱体添加到我们的模型中。 一个具有平面着色,另一个具有光滑着色,因此您可以看到区别。 这是我们在 MainWindow.xaml.cs 文件中的 Viewport3D
的 Loaded
事件中使用的圆柱体创建代码
Cylinder cylinder = new Cylinder(
new Point3D(0, WpfScene.sceneSize / 4, WpfScene.sceneSize / 5),
40, WpfScene.sceneSize / 8,
WpfScene.sceneSize / 8,
WpfScene.sceneSize / 6);
Cylinder cylinder2 = new Cylinder(
new Point3D(-WpfScene.sceneSize / 2, WpfScene.sceneSize / 4, 0),
40, WpfScene.sceneSize / 8,
WpfScene.sceneSize / 8,
WpfScene.sceneSize / 6);
GeometryModel3D cylinderModel = cylinder.CreateModel(Colors.AliceBlue, true, true);
GeometryModel3D cylinderModel2 = cylinder2.CreateModel(Colors.AliceBlue, true, false);
Model3DGroup groupScene = new Model3DGroup();
groupScene.Children.Add(cylinderModel);
groupScene.Children.Add(cylinderModel2);
我们使用与以前相同的方法来设置我们的圆圈和圆柱体的旋转
public void turnModel(Point3D center, GeometryModel3D model,
double beginAngle, double endAngle, double seconds, bool forever)
{
Vector3D vector = new Vector3D(0, 1, 0);
AxisAngleRotation3D rotation = new AxisAngleRotation3D(vector, 0.0);
DoubleAnimation doubleAnimation = new DoubleAnimation
(beginAngle, endAngle, durationTS(seconds));
if (forever)
{
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
}
doubleAnimation.BeginTime = durationTS(0.0);
RotateTransform3D rotateTransform = new RotateTransform3D(rotation, center);
model.Transform = rotateTransform;
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, doubleAnimation);
}
所以这是我们的 viewport
的整个 Loaded
事件,我们在其中创建我们的场景
private void Viewport3D_Loaded(object sender, RoutedEventArgs e)
{
if (sender is Viewport3D)
{
Viewport3D viewport = (Viewport3D)sender;
Cylinder cylinder = new Cylinder(
new Point3D(0, WpfScene.sceneSize / 4, WpfScene.sceneSize / 5),
40, WpfScene.sceneSize / 8,
WpfScene.sceneSize / 8,
WpfScene.sceneSize / 6);
Cylinder cylinder2 = new Cylinder(
new Point3D(-WpfScene.sceneSize / 2, WpfScene.sceneSize / 4, 0),
40, WpfScene.sceneSize / 8,
WpfScene.sceneSize / 8,
WpfScene.sceneSize / 6);
WpfCircle circle = new WpfCircle(55,
new Point3D(-WpfScene.sceneSize / 2, WpfScene.sceneSize / 6,
WpfScene.sceneSize / 2), WpfScene.sceneSize / 15);
WpfCircle circle2 = new WpfCircle(55,
new Point3D(0, WpfScene.sceneSize / 6, WpfScene.sceneSize / 2),
WpfScene.sceneSize / 15);
GeometryModel3D cylinderModel =
cylinder.CreateModel(Colors.AliceBlue, true, true);
GeometryModel3D cylinderModel2 =
cylinder2.CreateModel(Colors.AliceBlue, true, false);
GeometryModel3D circleModel = circle.createModel(Colors.Aqua, false);
GeometryModel3D circleModel2 = circle2.createModelTwoSided(Colors.Aqua, false);
double floorThickness = WpfScene.sceneSize / 100;
GeometryModel3D floorModel = WpfCube.CreateCubeModel(
new Point3D(-WpfScene.sceneSize / 2,
-floorThickness,
-WpfScene.sceneSize / 2),
WpfScene.sceneSize, floorThickness, WpfScene.sceneSize, Colors.Tan);
Model3DGroup groupScene = new Model3DGroup();
groupScene.Children.Add(floorModel);
groupScene.Children.Add(cylinderModel);
groupScene.Children.Add(cylinderModel2);
groupScene.Children.Add(circleModel);
groupScene.Children.Add(circleModel2);
groupScene.Children.Add(leftLight());
groupScene.Children.Add(new AmbientLight(Colors.Gray));
viewport.Camera = camera();
ModelVisual3D visual = new ModelVisual3D();
visual.Content = groupScene;
viewport.Children.Add(visual);
turnModel(cylinder.getCenter(), cylinderModel, 0, 360, 13, true);
turnModel(cylinder2.getCenter(), cylinderModel2, 0, 360, 13, true);
turnModel(circle.getCenter(), circleModel, 0, 360, 8, true);
turnModel(circle2.getCenter(), circleModel2, 0, 360, 8, true);
}
}
在本文中,我们添加了机器人需要的另外几个形状。 我们现在可以制作三角形、矩形、立方体、圆形和圆柱体。 我们还演示了如何在 WPF 中通过使用 BackMaterial
制作双面模型,以及如何通过组合顶点获得光滑着色而不是平面着色。
在我们的下一篇文章中,我们将添加机器人所需的最终形状类,然后我们将展示如何使用动画变换和故事板来使整个事物移动并使机器人栩栩如生。
历史
- 2010 年 11 月 4 日:初始版本