WPF:3D甜甜圈

一、坐标系

  • WPF 的 3D 坐标系原点通常位于对象中心,X 轴向右,Y 轴向上,Z 轴朝向观察者(右手定则)。

二、甜甜圈的几何生成

甜甜圈 ,即圆环体(Torus)的几何生成:

  • 参数方程:
    R:主半径(中心到管道中心线的距离)
    r:管道半径(截面圆的半径)
    表面点坐标:

x = (R + r·cosφ)·cosθ

y = (R + r·cosφ)·sinθ

z = r·sinφ

θ 绕 Z 轴(大圆),φ 绕管道中心(小圆)。

  • 法线:

    精确法线为 (cosφ·cosθ, cosφ·sinθ, sinφ),与表面垂直,保证光照正确。

  • 网格化:

    uSegments 和 vSegments 分别控制两个方向的分辨率,生成四边形网格,每个四边形由两个三角形组成,形成完整的三角网格。

三、交互控制

  • 视角控制:

    PerspectiveCamera 的 FieldOfView 绑定到焦距滑块,实现视野缩放。

  • 旋转控制:

    三个滑块分别绑定到 X、Y、Z 轴的 AxisAngleRotation3D,通过 ElementName 绑定实现双向同步。

  • 自动旋转:

    使用 DispatcherTimer 定时递增 rotateY.Value,触发 UI 更新,无需手动操作变换矩阵。

四、 光照与材质

  • 使用环境光 + 两个方向光提供柔和立体感。

  • 前后材质分别设为橙色和深橙色,便于观察背面。

五、 相机位置

  • 相机置于 (20, 10, 30),看向原点,适合显示半径 2 的圆环体。

六、MainWindow.xaml

复制代码
<Window x:Class="WpfA_Torus.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_Torus"
        mc:Ignorable="d"
         Title="甜甜圈 (Torus)" 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="45" Minimum="5" Maximum="120" />
            <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="TorusModel">
                            <GeometryModel3D.Material>
                                <DiffuseMaterial Brush="Orange" />
                            </GeometryModel3D.Material>
                            <GeometryModel3D.BackMaterial>
                                <DiffuseMaterial Brush="DarkOrange" />
                            </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="20,10,30" LookDirection="-20,-10,-30" 
                                   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_Torus
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private DispatcherTimer autoRotateTimer;
        private bool isAutoRotating = false;

        public MainWindow()
        {
            InitializeComponent();
            // 生成甜甜圈:主半径2.0,管半径0.8,大圆分段60,小圆分段30
            GenerateTorus(2.0, 0.8, 60, 30);
        }

        /// <summary>
        /// 生成圆环体网格数据
        /// </summary>
        /// <param name="R">主半径(中心到管道中心)</param>
        /// <param name="r">管道半径</param>
        /// <param name="uSegments">大圆周分段数</param>
        /// <param name="vSegments">截面圆周分段数</param>
        private void GenerateTorus(double R, double r, int uSegments, int vSegments)
        {
            int numVertices = (uSegments + 1) * (vSegments + 1);
            Point3D[] positions = new Point3D[numVertices];
            Vector3D[] normals = new Vector3D[numVertices];
            Point[] textureCoords = new Point[numVertices];

            int index = 0;
            for (int u = 0; u <= uSegments; u++)
            {
                double theta = u * 2 * Math.PI / uSegments; // 大圆角度
                for (int v = 0; v <= vSegments; v++)
                {
                    double phi = v * 2 * Math.PI / vSegments; // 小圆角度

                    // 圆环面参数方程
                    double x = (R + r * Math.Cos(phi)) * Math.Cos(theta);
                    double y = (R + r * Math.Cos(phi)) * Math.Sin(theta);
                    double z = r * Math.Sin(phi);

                    positions[index] = new Point3D(x, y, z);

                    // 法线方向(单位向量)
                    double nx = Math.Cos(phi) * Math.Cos(theta);
                    double ny = Math.Cos(phi) * Math.Sin(theta);
                    double nz = Math.Sin(phi);
                    normals[index] = new Vector3D(nx, ny, nz);

                    // 纹理坐标(用于贴图,此处仅作展示)
                    textureCoords[index] = new Point((double)u / uSegments, (double)v / vSegments);

                    index++;
                }
            }

            // 生成三角形索引(每个四边形分为两个三角形)
            int numTriangles = uSegments * vSegments * 2;
            int[] triangleIndices = new int[numTriangles * 3];
            int triIndex = 0;
            for (int u = 0; u < uSegments; u++)
            {
                for (int v = 0; v < vSegments; v++)
                {
                    int p0 = u * (vSegments + 1) + v;
                    int p1 = p0 + 1;
                    int p2 = (u + 1) * (vSegments + 1) + v;
                    int p3 = p2 + 1;

                    // 三角形1
                    triangleIndices[triIndex++] = p0;
                    triangleIndices[triIndex++] = p1;
                    triangleIndices[triIndex++] = p2;

                    // 三角形2
                    triangleIndices[triIndex++] = p1;
                    triangleIndices[triIndex++] = p3;
                    triangleIndices[triIndex++] = p2;
                }
            }

            // 构建MeshGeometry3D
            MeshGeometry3D mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(positions);
            mesh.Normals = new Vector3DCollection(normals);
            mesh.TextureCoordinates = new PointCollection(textureCoords);
            mesh.TriangleIndices = new Int32Collection(triangleIndices);

            // 赋给模型
            TorusModel.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/93217703

相关推荐
别催小唐敲代码6 小时前
ROS2从入门到实战:去中心化机器人操作系统详解
wpf·ros2
神秘的MT8 小时前
C# WinForm Socket 类 常用方法 (C# .NET,TCP 场景)
服务器·网络·学习·tcp/ip·c#·winform
神罚天下ET19 小时前
c# ACME client (补充)
开发语言·c#
噗噗夹的TA之旅1 天前
Shader 学习 21:自定义 Render Feature
学习·游戏·unity·c#·游戏引擎·图形渲染
脚踏实地皮皮晨1 天前
003006001_WrapPanel控件
开发语言·c#·.net·wpf·visual studio
脚踏实地皮皮晨1 天前
003005002_WPF StackPanel 基类官方类定义逐行深度解析
开发语言·windows·算法·c#·wpf·visual studio
用户298698530141 天前
Excel 转 PDF 免费在线工具,格式完美保留
人工智能·c#·excel
库拉库拉里1 天前
深入理解 C# 异步编程:同步、Task.Wait () 与 await 的本质区别及实践指南
开发语言·c#
神罚天下ET1 天前
.NET 新增功能系列文章——C# 中的新增功能
开发语言·c#·.net