WPF和3D创建碗效果

时间:2009-10-16 05:35:43

标签: wpf 3d effects perspective

如何使用WPF的3D组件或使用伪3D效果创建“碗”效果,用户俯视碗并可以拖动矩形并使矩形透视更改看起来它们在碗上向上,向下移动?我没有任何重力效应或任何东西,只要物品移动,我需要他们的视角进行调整......

编辑:我一直在研究WPF中可用的实际3D效果,它看起来非常强大,所以也许有人可以帮我在我的应用上获得一个半球,然后将一些3D网格(矩形)粘在它的表面上?

有什么想法吗?

谢谢, 标记

2 个答案:

答案 0 :(得分:3)

啊,对,你走了 - 你现在可以把红色长方形拖到碗边 - 享受吧!

<Window x:Class="wpfbowl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500"
    DataContext="{Binding RelativeSource={RelativeSource Self}}" MouseDown="Window_MouseDown" MouseMove="Window_MouseMove" MouseUp="Window_MouseUp" >
<Window.Resources>
    <Transform3DGroup x:Key="WorldTrans">

        <RotateTransform3D>
            <RotateTransform3D.Rotation>
                <AxisAngleRotation3D x:Name="myAngleRotation" Axis="0,0,1" Angle="{Binding RotationLeftRight}" />

            </RotateTransform3D.Rotation>

        </RotateTransform3D>
        <RotateTransform3D>
            <RotateTransform3D.Rotation>
                <AxisAngleRotation3D x:Name="myAngleRotation2" Axis="1,0,0" Angle="{Binding RotationUpDown}" />

            </RotateTransform3D.Rotation>

        </RotateTransform3D>
    </Transform3DGroup>
</Window.Resources>
    <StackPanel>
    <Viewport3D Name="mainViewport" ClipToBounds="True" HorizontalAlignment="Stretch" Margin="0" Height="500" >
        <Viewport3D.Camera>
            <PerspectiveCamera 
          LookDirection="0,5,0"
          UpDirection="0,0,1"
          Position="0,-10,0" 
          />
        </Viewport3D.Camera>

        <ModelVisual3D  >
        <ModelVisual3D>
            <ModelVisual3D.Content>

                <Model3DGroup>

                    <PointLight Position="0,-10,0" Range="150" Color="White" />


                    </Model3DGroup>

            </ModelVisual3D.Content>
        </ModelVisual3D>
    </ModelVisual3D>


    <ModelVisual3D Transform="{StaticResource WorldTrans}">
        <ModelVisual3D Content="{Binding Models}">

        </ModelVisual3D>
    </ModelVisual3D>
        <ModelVisual3D >
            <ModelVisual3D Content="{Binding BowlModel}">

            </ModelVisual3D>
        </ModelVisual3D>

    </Viewport3D>
    </StackPanel>
</Window>

和背后的代码......

using System;
using System.ComponentModel;
using System.Timers;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Input;

namespace wpfbowl
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : INotifyPropertyChanged
{
    public Window1()
    {
        InitModels();
        InitializeComponent();

    }

    private Model3DGroup _cube;
    private bool _cubeSelected;
    private bool _cubeMoving;
    private Point3D _startPoint;
    private Point3D _currentPoint;

    public void InitModels()
    {
        const int bowlQuality = 20;
        Models = new Model3DGroup();
        BowlModel = new Model3DGroup();

        _cube = GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 2.6, 0), new Size3D(1.5, 0.2, 2));
        Models.Children.Add(_cube);

        var bowl = CreateBowl(new Point3D(0, 0, 0), 3, bowlQuality, bowlQuality, GetSurfaceMaterial(Colors.Green));
        BowlModel.Children.Add(bowl);
    }


    private readonly Timer _timer;

    public Model3DGroup Models { get; set; }

    public Model3DGroup BowlModel { get; set; }

    private double _rotationLeftRight;
    public double RotationLeftRight
    {
        get { return _rotationLeftRight; }
        set
        {
            if (_rotationLeftRight == value) return;
            _rotationLeftRight = value;
            OnPropertyChanged("RotationLeftRight");
        }
    }

    private double _rotationUpDown;
    public double RotationUpDown
    {
        get { return _rotationUpDown; }
        set
        {
            if (_rotationUpDown == value) return;
            _rotationUpDown = value;
            OnPropertyChanged("RotationUpDown");
        }
    }


    public static Model3DGroup CreateBowl(Point3D center, double radius, int u, int v, MaterialGroup materialGroup)
    {
        var bowl = new Model3DGroup();
        if (u < 2 || v < 2) return null;
        var pts = new Point3D[u, v];
        for (var i = 0; i < u; i++)
        {
            for (var j = 0; j < v; j++)
            {
                pts[i, j] = GetPosition(radius, i * 180 / (u - 1), j * 360 / (v - 1));
                pts[i, j] += (Vector3D)center;
            }
        }

        var p = new Point3D[4];
        for (var i = 0; i < (u /2) - 1; i++)
        {
            for (var j = 0; j <  v   - 1; j++)
            {
                p[0] = pts[i, j];
                p[1] = pts[i + 1, j];
                p[2] = pts[i + 1, j + 1];
                p[3] = pts[i, j + 1];
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[1], p[2]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[1], p[0]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[3], p[0]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[3], p[2]));
            }
        }
        return bowl;
    }


    private static Model3DGroup CreateTriangleModel(Material material, Point3D p0, Point3D p1, Point3D p2)
    {
        var mesh = new MeshGeometry3D();
        mesh.Positions.Add(p0);
        mesh.Positions.Add(p1);
        mesh.Positions.Add(p2);
        mesh.TriangleIndices.Add(0);
        mesh.TriangleIndices.Add(1);
        mesh.TriangleIndices.Add(2);
        var normal = CalculateNormal(p0, p1, p2);
        mesh.Normals.Add(normal);
        mesh.Normals.Add(normal);
        mesh.Normals.Add(normal);

        var model = new GeometryModel3D(mesh, material);

        var group = new Model3DGroup();
        group.Children.Add(model);
        return group;
    }

    private static Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
    {
        var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
        var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
        return Vector3D.CrossProduct(v0, v1);
    }

    private static Point3D GetPosition(double radius, double theta, double phi)
    {
        var pt = new Point3D();
        var snt = Math.Sin(theta * Math.PI / 180);
        var cnt = Math.Cos(theta * Math.PI / 180);
        var snp = Math.Sin(phi * Math.PI / 180);
        var cnp = Math.Cos(phi * Math.PI / 180);
        pt.X = radius * snt * cnp;
        pt.Y = radius * cnt;
        pt.Z = -radius * snt * snp;
        return pt;
    }

    public static MaterialGroup GetSurfaceMaterial(Color colour)
    {
        var materialGroup = new MaterialGroup();
        var emmMat = new EmissiveMaterial(new SolidColorBrush(colour));
        materialGroup.Children.Add(emmMat);
        materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
        var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
        materialGroup.Children.Add(specMat);
        return materialGroup;
    }

    public static Model3DGroup GetCube(MaterialGroup materialGroup, Point3D point, Size3D size)
    {
        var farPoint = new Point3D(point.X - (size.X / 2), point.Y - (size.Y / 2), point.Z - (size.Z / 2));
        var nearPoint = new Point3D(point.X + (size.X / 2), point.Y + (size.Y / 2), point.Z + (size.Z / 2));
        var cube = new Model3DGroup();

        var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
        var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
        var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
        var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
        var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
        var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
        var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
        var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
        //front side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p3, p2, p6));
        cube.Children.Add(CreateTriangleModel(materialGroup, p3, p6, p7));
        //right side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p1, p5));
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p5, p6));
        //back side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p1, p0, p4));
        cube.Children.Add(CreateTriangleModel(materialGroup, p1, p4, p5));
        //left side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p0, p3, p7));
        cube.Children.Add(CreateTriangleModel(materialGroup, p0, p7, p4));
        //top side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p7, p6, p5));
        cube.Children.Add(CreateTriangleModel(materialGroup, p7, p5, p4));
        //bottom side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p3, p0));
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p0, p1));
        return cube;
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {

            var mousePos = e.GetPosition(mainViewport);
            var hitParams = new PointHitTestParameters(mousePos);
            VisualTreeHelper.HitTest(mainViewport, null, ResultCallback, hitParams);


    }

    public HitTestResultBehavior ResultCallback(HitTestResult result)
    {
        // Did we hit 3D?
        var rayResult = result as RayHitTestResult;
        if (rayResult != null)
        {
            // Did we hit a MeshGeometry3D?
            var rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;

            if (rayMeshResult != null)
            {
                if (_cubeSelected)
                {
                    _cubeMoving = true;
                    _currentPoint = rayMeshResult.PointHit;
                    RotationLeftRight = (_startPoint.X - _currentPoint.X) * 15;
                    RotationUpDown = (_currentPoint.Z -_startPoint.Z)*15;
                }
                else
                {
                    var model = rayMeshResult.ModelHit;
                    foreach (var c in _cube.Children)
                    {
                        if (c.GetType() != typeof(Model3DGroup)) continue;
                        var model3DGroup = (Model3DGroup)c;
                        foreach (var sc in model3DGroup.Children)
                        {
                            if (model != sc) continue;

                            _cubeSelected = true;
                            _startPoint = rayMeshResult.PointHit;

                        }
                    } 
                }

            }
        }
        return HitTestResultBehavior.Continue;
    }

    private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    {
        if (!_cubeSelected) return;
        var mousePos = e.GetPosition(mainViewport);
        var hitParams = new PointHitTestParameters(mousePos);
        VisualTreeHelper.HitTest(mainViewport, null, ResultCallback, hitParams);
    }

    private void Window_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (!_cubeSelected) return;
        _cubeSelected = false;
        _cubeMoving = false;
    }

}
}

答案 1 :(得分:1)

在这里,我不太确定你对矩形的意思,所以我只是在绿色碗的开口处添加了四个红色矩形。

干杯,

安迪

首先是Xaml ......

<Window x:Class="wpfbowl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
    <Transform3DGroup x:Key="WorldTrans">

        <RotateTransform3D>
            <RotateTransform3D.Rotation>
                <AxisAngleRotation3D x:Name="myAngleRotation" Axis="0,0,1" Angle="{Binding Rotation}" />
            </RotateTransform3D.Rotation>
        </RotateTransform3D>
    </Transform3DGroup>
</Window.Resources>
    <StackPanel>
    <Viewport3D Name="mainViewport" ClipToBounds="True" HorizontalAlignment="Stretch" Margin="0" Height="500" >
        <Viewport3D.Camera>
            <PerspectiveCamera 
          LookDirection="0,5,0"
          UpDirection="0,0,1"
          Position="0,-10,0" 
          />
        </Viewport3D.Camera>

        <ModelVisual3D  >
        <ModelVisual3D>
            <ModelVisual3D.Content>

                <Model3DGroup>

                    <PointLight Position="0,-10,0" Range="150" Color="White" />


                    </Model3DGroup>

            </ModelVisual3D.Content>
        </ModelVisual3D>
    </ModelVisual3D>


    <ModelVisual3D Transform="{StaticResource WorldTrans}">
        <ModelVisual3D Content="{Binding Models}">

        </ModelVisual3D>
    </ModelVisual3D>

</Viewport3D>
    </StackPanel>
</Window>

......并且背后的代码......

using System;
using System.ComponentModel;
using System.Timers;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;

namespace wpfbowl
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : INotifyPropertyChanged
{
    public Window1()
    {
        InitModels();
        InitializeComponent();

        _timer = new Timer(100);
        _timer.Elapsed += TimerElapsed;
        _timer.Enabled = true;
    }

    void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        Dispatcher.Invoke(DispatcherPriority.Normal, new Action<double>(Transform), 2);
    }

    private void Transform(double value)
    {
        Rotation += value;
    }

    public void InitModels()
    {
        const int bowlQuality = 20;
        Models = new Model3DGroup();
        var sphere = CreateBowl(new Point3D(0, 0, 0), 3, bowlQuality, bowlQuality, GetSurfaceMaterial(Colors.Green));

        Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(3, 0, 0), new Size3D(1.5, 0.2, 2)));
        Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(-3, 0, 0), new Size3D(1.5, 0.2, 2)));
        Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 0, 3), new Size3D(1.5, 0.2, 2)));
        Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 0, -3), new Size3D(1.5, 0.2, 2)));
        Models.Children.Add(sphere);
    }


    private readonly Timer _timer;

    public Model3DGroup Models { get; set; }
    private double _rotation;
    public double Rotation
    {
        get { return _rotation; }
        set
        {
            if (_rotation == value) return;
            _rotation = value;
            OnPropertyChanged("Rotation");
        }
    }

    public static Model3DGroup CreateBowl(Point3D center, double radius, int u, int v, MaterialGroup materialGroup)
    {
        var bowl = new Model3DGroup();
        if (u < 2 || v < 2) return null;
        var pts = new Point3D[u, v];
        for (var i = 0; i < u; i++)
        {
            for (var j = 0; j < v; j++)
            {
                pts[i, j] = GetPosition(radius, i * 180 / (u - 1), j * 360 / (v - 1));
                pts[i, j] += (Vector3D)center;
            }
        }

        var p = new Point3D[4];
        for (var i = 0; i < (u /2) - 1; i++)
        {
            for (var j = 0; j <  v   - 1; j++)
            {
                p[0] = pts[i, j];
                p[1] = pts[i + 1, j];
                p[2] = pts[i + 1, j + 1];
                p[3] = pts[i, j + 1];
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[1], p[2]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[1], p[0]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[3], p[0]));
                bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[3], p[2]));
            }
        }
        return bowl;
    }


    private static Model3DGroup CreateTriangleModel(Material material, Point3D p0, Point3D p1, Point3D p2)
    {
        var mesh = new MeshGeometry3D();
        mesh.Positions.Add(p0);
        mesh.Positions.Add(p1);
        mesh.Positions.Add(p2);
        mesh.TriangleIndices.Add(0);
        mesh.TriangleIndices.Add(1);
        mesh.TriangleIndices.Add(2);
        var normal = CalculateNormal(p0, p1, p2);
        mesh.Normals.Add(normal);
        mesh.Normals.Add(normal);
        mesh.Normals.Add(normal);

        var model = new GeometryModel3D(mesh, material);

        var group = new Model3DGroup();
        group.Children.Add(model);
        return group;
    }

    private static Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
    {
        var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
        var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
        return Vector3D.CrossProduct(v0, v1);
    }

    private static Point3D GetPosition(double radius, double theta, double phi)
    {
        var pt = new Point3D();
        var snt = Math.Sin(theta * Math.PI / 180);
        var cnt = Math.Cos(theta * Math.PI / 180);
        var snp = Math.Sin(phi * Math.PI / 180);
        var cnp = Math.Cos(phi * Math.PI / 180);
        pt.X = radius * snt * cnp;
        pt.Y = radius * cnt;
        pt.Z = -radius * snt * snp;
        return pt;
    }

    public static MaterialGroup GetSurfaceMaterial(Color colour)
    {
        var materialGroup = new MaterialGroup();
        var emmMat = new EmissiveMaterial(new SolidColorBrush(colour));
        materialGroup.Children.Add(emmMat);
        materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
        var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
        materialGroup.Children.Add(specMat);
        return materialGroup;
    }

    public static Model3DGroup GetCube(MaterialGroup materialGroup, Point3D point, Size3D size)
    {
        var farPoint = new Point3D(point.X - (size.X / 2), point.Y - (size.Y / 2), point.Z - (size.Z / 2));
        var nearPoint = new Point3D(point.X + (size.X / 2), point.Y + (size.Y / 2), point.Z + (size.Z / 2));
        var cube = new Model3DGroup();

        var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
        var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
        var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
        var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
        var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
        var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
        var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
        var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
        //front side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p3, p2, p6));
        cube.Children.Add(CreateTriangleModel(materialGroup, p3, p6, p7));
        //right side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p1, p5));
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p5, p6));
        //back side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p1, p0, p4));
        cube.Children.Add(CreateTriangleModel(materialGroup, p1, p4, p5));
        //left side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p0, p3, p7));
        cube.Children.Add(CreateTriangleModel(materialGroup, p0, p7, p4));
        //top side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p7, p6, p5));
        cube.Children.Add(CreateTriangleModel(materialGroup, p7, p5, p4));
        //bottom side triangles
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p3, p0));
        cube.Children.Add(CreateTriangleModel(materialGroup, p2, p0, p1));
        return cube;
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

}
}