WPF and 3D to create a bowl effect

How can you use either WPF 3D components or use a pseudo 3D effect to create a "Bowl" effect where the user is looking down at the bowl and can drag the rectangles and change perspectives in the rectangles that appear to move up, down and around the bowl? I don't experience any gravitational effects or anything else, only when objects are moving I need their perspective to adjust ...

EDIT: I'm looking into the actual 3D effects available in WPF, which seem to be very powerful, so maybe someone can help get a half sphere in my application and then paint some 3D meshes (rectangles) on the surface?

Any thoughts?

Thanks Mark

+2


source to share


2 answers


Ah yes, here you go - you can now drag the red rectangle around the bowl - enjoy!

<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>

      



and the code behind ...

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;
    }

}
}

      

+3


source


Here you go, I didn't quite understand what you meant about rectangles, so I just added four red rectangles around the opening of the green bowl.

Greetings,

Andy

Xaml first ...



<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>

      

... and hides the code behind ...

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

}
}

      

+1


source







All Articles