一、坐标系
- WPF 的 3D 坐标系原点通常位于对象中心,X 轴向右,Y 轴向上,Z 轴朝向观察者(右手定则)。
正四面体旋转以原点为中心,绕X,Y,Z 轴自动旋转。
二、正二十面体的坐标
1. 正二十面体的几何特征
- 顶点数:12
- 面数:20,每个面都是正三角形
- 边数:60
- 对偶多面体:正十二面体(12个顶点 ↔ 12个面)
2. 顶点坐标的数学构造
- 标准坐标: (±1, ±φ, 0), (0, ±1, ±φ), (±φ, 0, ±1) 其中φ=(1+√5)/2
顶点坐标
0 = (-1, φ, 0)
1 = (1, φ, 0)
2 = (-1, -φ, 0)
3 = (1, -φ, 0)
4 = (0, -1, φ)
5 = (0, 1, φ)
6 = (0, -1, -φ)
7 = (0, 1, -φ)
8 = (φ, 0, -1)
9 = (φ, 0, 1)
10 = (-φ, 0, -1)
11 = (-φ, 0, 1)
- 定义三角形面:每个面由三个顶点索引组成。
正20面体有20个面,每个面3个顶点,共60个索引。
三角形面:面:
{0, 11, 5}
{0, 5, 1}
{0, 1, 7}
{0, 7, 10}
{0, 10, 11}
{1, 5, 9}
{5, 11, 4}
{11, 10, 2}
{10, 7, 6}
{7, 1, 8}
{3, 9, 4}
{3, 4, 2}
{3, 2, 6}
{3, 6, 8}
{3, 8, 9}
{4, 9, 5}
{2, 4, 11}
{6, 2, 10}
{8, 6, 7}
{9, 8, 1}
0 个面包含 {0,11,5}、{0,5,1}、{0,1,7}、{0,7,10}、{0,10,11} -> 围绕顶点 0 的 5 个面(度数 5)。完美!
1 个面包含 {0,5,1}、{0,1,7}、{1,5,9}、{9,8,1}、{7,1,8} -> 围绕顶点 1 的 5 个面。完美!
-
在 C# 代码中动态构建
三、MainWindow.xaml
<Window x:Class="WpfA_Icosahedron.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfA_Icosahedron"
mc:Ignorable="d"
Title="正二十面体" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<!-- 右侧控制面板 -->
<StackPanel Grid.Column="1" Margin="10">
<TextBlock Text="调节相机焦距" Margin="0,0,0,5"/>
<Slider x:Name="viewAngle" Width="200" Value="10" Minimum="5" Maximum="100" />
<TextBox Text="{Binding ElementName=viewAngle, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="X轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateX" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateX, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="Y轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateY" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateY, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="Z轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateZ" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateZ, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<Button x:Name="btnAutoRotate" Content="启动自动旋转" Width="120" Margin="0,20,0,5" Click="BtnAutoRotate_Click"/>
<Button x:Name="btnExit" Content="退出程序" Width="120" Click="BtnExit_Click"/>
</StackPanel>
<Viewport3D Grid.Column="0">
<!-- 模型容器,并应用旋转转换(绑定滑块) -->
<ModelVisual3D x:Name="modelVisual">
<ModelVisual3D.Content>
<Model3DGroup>
<!-- 我们的正二十面体模型,几何数据在后台生成 -->
<GeometryModel3D x:Name="IcosahedronModel">
<GeometryModel3D.Material>
<DiffuseMaterial Brush="DodgerBlue" />
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush="DarkBlue" />
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
<!-- 光源 -->
<AmbientLight Color="#404040" />
<DirectionalLight Color="White" Direction="1, -3, -2" />
<DirectionalLight Color="Gray" Direction="-1, 2, 3" />
</Model3DGroup>
</ModelVisual3D.Content>
<!-- 旋转变换(保持完全一致) -->
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1,0,0" Angle="{Binding Value, ElementName=rotateX}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,1,0" Angle="{Binding Value, ElementName=rotateY}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,0,1" Angle="{Binding Value, ElementName=rotateZ}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="25,15,40" LookDirection="-25,-15,-40"
FieldOfView="{Binding Value, ElementName=viewAngle}"
UpDirection="0,1,0"/>
</Viewport3D.Camera>
</Viewport3D>
</Grid>
</Window>
四、MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
namespace WpfA_Icosahedron
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DispatcherTimer? _rotationTimer; // 可为 null
private const double RotationStep = 0.5; // 每帧旋转角度增量
public MainWindow()
{
InitializeComponent();
// 设置初始旋转角度,让模型有一个良好的立体视角
rotateX.Value = 20;
rotateY.Value = -30;
rotateZ.Value = 0;
BuildIcosahedron();
// 初始化定时器
_rotationTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(30) // 约 33 帧/秒
};
_rotationTimer.Tick += RotationTimer_Tick;
}
/// <summary>
/// 构建正二十面体的3D模型
/// </summary>
private void BuildIcosahedron()
{
// 1. 黄金比例
double phi = (1 + Math.Sqrt(5)) / 2; // ≈ 1.618
// 2. 定义正二十面体的 12 个基础顶点
// 坐标模式:(0, ±1, ±φ), (±1, ±φ, 0), (±φ, 0, ±1)
Point3D[] baseVertices = new Point3D[]
{
new Point3D(-1, phi, 0), // 0
new Point3D( 1, phi, 0), // 1
new Point3D(-1, -phi, 0), // 2
new Point3D( 1, -phi, 0), // 3
new Point3D( 0, -1, phi), // 4
new Point3D( 0, 1, phi), // 5
new Point3D( 0, -1, -phi), // 6
new Point3D( 0, 1, -phi), // 7
new Point3D( phi, 0, -1), // 8
new Point3D( phi, 0, 1), // 9
new Point3D(-phi, 0, -1), // 10
new Point3D(-phi, 0, 1) // 11
};
// 3. 定义 20 个三角形的顶点索引(每个三角形引用上面 12 个顶点中的 3 个)
int[][] triangles = new int[][]
{
new int[]{0, 11, 5}, new int[]{0, 5, 1}, new int[]{0, 1, 7},
new int[]{0, 7, 10}, new int[]{0, 10, 11}, new int[]{1, 5, 9},
new int[]{5, 11, 4}, new int[]{11, 10, 2}, new int[]{10, 7, 6},
new int[]{7, 1, 8}, new int[]{3, 9, 4}, new int[]{3, 4, 2},
new int[]{3, 2, 6}, new int[]{3, 6, 8}, new int[]{3, 8, 9},
new int[]{4, 9, 5}, new int[]{2, 4, 11}, new int[]{6, 2, 10},
new int[]{8, 6, 7}, new int[]{9, 8, 1}
};
// 4. 构建网格(为了获得清晰的棱边效果,每个三角形使用独立的顶点,不共享)
MeshGeometry3D mesh = new MeshGeometry3D();
int index = 0;
foreach (var tri in triangles)
{
// 依次添加三角形的三个顶点
mesh.Positions.Add(baseVertices[tri[0]]);
mesh.Positions.Add(baseVertices[tri[1]]);
mesh.Positions.Add(baseVertices[tri[2]]);
// 添加三个索引(因为是顺序添加,所以索引连续递增)
mesh.TriangleIndices.Add(index++);
mesh.TriangleIndices.Add(index++);
mesh.TriangleIndices.Add(index++);
}
// 5. 将生成的网格赋值给 XAML 中定义的模型
IcosahedronModel.Geometry = mesh;
}
/// <summary>
/// 启动/停止自动旋转
/// </summary>
// 自动旋转按钮点击事件
private void BtnAutoRotate_Click(object sender, RoutedEventArgs e)
{
if (_rotationTimer == null)
{
// 创建定时器,每 50ms 触发一次(约 20fps)
_rotationTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(50)
};
_rotationTimer.Tick += RotationTimer_Tick;
_rotationTimer.Start();
btnAutoRotate.Content = "停止自动旋转";
}
else
{
_rotationTimer.Stop();
_rotationTimer.Tick -= RotationTimer_Tick;
_rotationTimer = null;
btnAutoRotate.Content = "启动自动旋转";
}
}
// 定时器 Tick:累加 Z 轴角度
// 注意这里的 sender 参数类型为 object?,与 EventHandler 委托匹配
private void RotationTimer_Tick(object? sender, EventArgs e)
{
// 三个角度每次增加 1 度(可调整速度)
rotateX.Value = (rotateX.Value + 1) % 360;
rotateY.Value = (rotateY.Value + 1) % 360;
rotateZ.Value = (rotateZ.Value + 1) % 360;
}
/// <summary>
/// 退出程序
/// </summary>
private void BtnExit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
