WPF:3D扭扭圈 一、坐标系WPF 的 3D 坐标系原点通常位于对象中心X 轴向右Y 轴向上Z 轴朝向观察者右手定则。二、亏格Genus概念亏格是曲面“洞”的数目。标准甜甜圈环面有1个洞亏格1。亏格2的曲面有两个洞外形类似于双环面或“∞”字形管状结构。拓扑上亏格2曲面的欧拉示性数χ 2 - 2g -2。三、几何生成方法扫掠Sweep我们采用路径扫掠技术用一条平面“∞”字形路径Lemniscate of Bernoulli扫掠出一个圆形截面形成管状曲面。路径参数方程Lemniscate当t从0到2π时路径在原点交叉形成“8”字形两个环。扫掠过程在路径上离散采样点pathSegments个分段。计算每个点的切向量数值微分并利用平面曲线的性质副法向量恒为Z轴得到法向量和副法向量。在每个路径点处以该点为圆心在垂直于切向量的平面内构造一个圆crossSegments个分段。将所有圆上的点作为网格顶点并连接相邻路径点、相邻截面点形成三角形网格。法线计算顶点法线近似取从路径中心指向截面点的方向这保证了光照效果的平滑。四、与标准环面的对比标准环面亏格1的路径是一个圆扫掠后形成一个环形管道。亏格2曲面的路径是“8”字形扫掠后产生两个环在中心相连的结构整体为一个连通曲面且有两个洞满足亏格2的定义。五、程序实现细节数值微分使用前后差分计算切向量避免解析求导的复杂运算。Frenet框架对于平面曲线直接设置副法线为Z轴法线为副法线与切线的叉积简单可靠。网格构建顶点数组和三角形索引的生成方式与标准环面一致只需将圆形路径替换为“8”字形路径。六、MainWindow.xaml.csusing System; using System.Windows; using System.Windows.Media; using System.Windows.Media.Media3D; using System.Windows.Threading; namespace WpfA_Genus2Torus { /// summary /// Interaction logic for MainWindow.xaml /// /summary public partial class MainWindow : Window { private DispatcherTimer autoRotateTimer; private bool isAutoRotating false; public MainWindow() { InitializeComponent(); GenerateGenus2Torus(); // 生成亏格2甜甜圈 } /// summary /// 生成亏格2的双环面使用Lemniscate路径扫掠 /// /summary private void GenerateGenus2Torus() { int pathSegments 120; // 路径分段数 int crossSegments 30; // 截面圆周分段数 double a 2.5; // 路径尺度 double r 0.7; // 管道半径 // --- 1. 计算路径点及切向量数值微分--- Point3D[] pathPoints new Point3D[pathSegments 1]; Vector3D[] tangents new Vector3D[pathSegments 1]; // Lemniscate of Bernoulli 参数方程 Funcdouble, Point3D pathFunc t { double denom 1 Math.Sin(t) * Math.Sin(t); double x a * Math.Cos(t) / denom; double y a * Math.Sin(t) * Math.Cos(t) / denom; return new Point3D(x, y, 0); }; // 切向量数值微分 Funcdouble, Vector3D tangentFunc t { double dt 0.001; Point3D p1 pathFunc(t - dt); Point3D p2 pathFunc(t dt); Vector3D tan p2 - p1; tan.Normalize(); return tan; }; for (int i 0; i pathSegments; i) { double t (double)i / pathSegments * 2 * Math.PI; pathPoints[i] pathFunc(t); tangents[i] tangentFunc(t); } // --- 2. 计算法向量和副法向量平面曲线副法向量为Z轴--- Vector3D up new Vector3D(0, 0, 1); Vector3D[] normals new Vector3D[pathSegments 1]; Vector3D[] binormals new Vector3D[pathSegments 1]; for (int i 0; i pathSegments; i) { Vector3D T tangents[i]; // 法向量 up × T若T平行于Z则用X轴替代 Vector3D N Vector3D.CrossProduct(up, T); if (N.Length 1e-6) N new Vector3D(1, 0, 0); N.Normalize(); Vector3D B Vector3D.CrossProduct(T, N); B.Normalize(); normals[i] N; binormals[i] B; } // --- 3. 生成顶点 --- int numVertices (pathSegments 1) * (crossSegments 1); Point3D[] positions new Point3D[numVertices]; Vector3D[] vertexNormals new Vector3D[numVertices]; Point[] texCoords new Point[numVertices]; int idx 0; for (int i 0; i pathSegments; i) { for (int j 0; j crossSegments; j) { double theta (double)j / crossSegments * 2 * Math.PI; Point3D center pathPoints[i]; Vector3D N normals[i]; Vector3D B binormals[i]; // 截面圆上的点 double cosT Math.Cos(theta); double sinT Math.Sin(theta); Point3D pos center r * (cosT * N sinT * B); positions[idx] pos; // 法线从路径中心指向截面点的方向近似 Vector3D normal pos - center; if (normal.Length 1e-6) normal.Normalize(); else normal new Vector3D(0, 0, 1); vertexNormals[idx] normal; texCoords[idx] new Point((double)i / pathSegments, (double)j / crossSegments); idx; } } // --- 4. 生成三角形索引 --- int numTriangles pathSegments * crossSegments * 2; int[] triangleIndices new int[numTriangles * 3]; int triIdx 0; for (int i 0; i pathSegments; i) { for (int j 0; j crossSegments; j) { int p0 i * (crossSegments 1) j; int p1 p0 1; int p2 (i 1) * (crossSegments 1) j; int p3 p2 1; // 两个三角形构成一个四边形 triangleIndices[triIdx] p0; triangleIndices[triIdx] p1; triangleIndices[triIdx] p2; triangleIndices[triIdx] p1; triangleIndices[triIdx] p3; triangleIndices[triIdx] p2; } } // --- 5. 构建MeshGeometry3D并赋值 --- MeshGeometry3D mesh new MeshGeometry3D(); mesh.Positions new Point3DCollection(positions); mesh.Normals new Vector3DCollection(vertexNormals); mesh.TextureCoordinates new PointCollection(texCoords); mesh.TriangleIndices new Int32Collection(triangleIndices); TorusGenus2Model.Geometry mesh; } // 自动旋转按钮事件与之前相同 private void BtnAutoRotate_Click(object sender, RoutedEventArgs e) { if (!isAutoRotating) { autoRotateTimer new DispatcherTimer(); autoRotateTimer.Interval TimeSpan.FromMilliseconds(30); autoRotateTimer.Tick (s, args) { // 沿X,Y,Z轴每次增加1度 rotateX.Value (rotateX.Value 1) % 360; rotateY.Value (rotateY.Value 1) % 360; rotateZ.Value (rotateZ.Value 1) % 360; }; autoRotateTimer.Start(); isAutoRotating true; btnAutoRotate.Content 停止自动旋转; } else { autoRotateTimer?.Stop(); isAutoRotating false; btnAutoRotate.Content 启动自动旋转; } } private void BtnExit_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } } }七、源码下载https://download.csdn.net/download/dalong10/93222582