AdSense

Thursday 25 July 2013

C# WPF - 3D graphics

(Deutsche Version) Until now, I was not able to create 3D graphics. Today I found out that WPF is able to do this, too. Within 2 hours, I was able to create a small tank who can be controlled with the arrow keys. Creating 3D graphics with WPF is extremely simpel.

As you can see in the code, the main part of the program is the function Tank(), which creates all objects by hand. Basically, it is much easier to do these things with a 3D editor but for the moment, this will do fine.

Other functions I use are moveThread, this function moves the the tank and the function RefreshEverything which changes the position of the displayed tank. Here is the code for MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Wpf3dTest1
{
    public partial class MainWindow : Window
    {
        Dictionary<Key, Boolean> pressedKeys = new Dictionary<Key, Boolean>();
        bool quit = false;
        public MainWindow()
        {
            InitializeComponent();

            pressedKeys.Add(Key.Left, false);
            pressedKeys.Add(Key.Right, false);
            pressedKeys.Add(Key.Up, false);
            pressedKeys.Add(Key.Down, false);

            this.KeyDown += MainWindow_KeyDown;
            this.KeyUp += MainWindow_KeyUp;

            this.Closing += OnWindowClosing;

            //create a Tank object and add to viewport
            mod = Tank(new Point3D(xPos, 0, 0), 2.2, 1.8, 0.7, 0.3, 0.25, 0.5, 0.25, new DiffuseMaterial(Brushes.Green), new DiffuseMaterial(Brushes.Black));
            viewport3D.Children.Add(mod);

            //start a Thread which moves the object
            Thread thread = new Thread(moveThread);
            thread.Start();
        }

        void MainWindow_KeyUp(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = false;
            }
        }

        void MainWindow_KeyDown(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = true;
            }
        }

        ModelVisual3D mod;

        private delegate void RefreshEverythingDelegate();
        private void RefreshEverything()
        {
            TranslateTransform3D translate = new TranslateTransform3D();
            translate.OffsetX = xPos;
            translate.OffsetZ = zPos;

            RotateTransform3D rotate = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1,0),angle));
            rotate.CenterX = xPos;
            rotate.CenterZ = zPos;


            Transform3DGroup transform = new Transform3DGroup();
            transform.Children.Add(translate);
            transform.Children.Add(rotate);
            mod.Transform = transform;
        }

        //Parameters for turn and move
        private double posIncrementor = 0.02;
        private double xPos = 0;
        private double zPos = 0;

        private double angle = 0;
        private double angleIncrementor = 0.4;

        private void moveThread()
        {
            while (!quit)
            {
                if (pressedKeys[Key.Up])
                {
                    xPos += posIncrementor * Math.Cos(angle * Math.PI/180) ;
                    zPos -= posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Down])
                {
                    xPos -= posIncrementor * Math.Cos(angle * Math.PI / 180);
                    zPos += posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Left])
                {
                    angle += angleIncrementor;
                }
                if (pressedKeys[Key.Right])
                {
                    angle -= angleIncrementor;
                }
                DispatcherOperation dispOp = this.viewport3D.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new RefreshEverythingDelegate(RefreshEverything));
                Thread.Sleep(10);
            }
        }

        public void OnWindowClosing(object sender, CancelEventArgs e)
        {
            quit = true;
        }

        private ModelVisual3D Tank(Point3D position, double length, double width, double height, double chainWidth, double chainHeight1, double chainHeight2, double chainNotLength, Material bodyMaterial, Material chainMaterial)
        {
            //Tank Body
            MeshGeometry3D bodyMesh = new MeshGeometry3D();
            Point3D a = new Point3D(position.X - length / 2, 0, position.Z + width / 2);
            Point3D b = new Point3D(position.X + length / 2, 0, position.Z + width / 2);
            Point3D c = new Point3D(position.X + length / 2, 0, position.Z - width / 2);
            Point3D d = new Point3D(position.X - length / 2, 0, position.Z - width / 2);
            Point3D e = new Point3D(position.X - length / 2, position.Y + height, position.Z + width / 2);
            Point3D f = new Point3D(position.X + length / 2, position.Y + height, position.Z + width / 2);
            Point3D g = new Point3D(position.X + length / 2, position.Y + height, position.Z - width / 2);
            Point3D h = new Point3D(position.X - length / 2, position.Y + height, position.Z - width / 2);
            BuildRectangle(bodyMesh, a, b, f, e, new Vector3D(0, 0, 1));
            BuildRectangle(bodyMesh, b, c, g, f, new Vector3D(1, 0, 0));
            BuildRectangle(bodyMesh, c, d, h, g, new Vector3D(0, 0, -1));
            BuildRectangle(bodyMesh, d, a, e, h, new Vector3D(-1, 0, 0));
            BuildRectangle(bodyMesh, e, f, g, h, new Vector3D(0, 1, 0));
            BuildRectangle(bodyMesh, a, d, c, b, new Vector3D(0, -1, 0));

            //Build the model object
            GeometryModel3D BodyModel = new GeometryModel3D(
            bodyMesh,
            bodyMaterial);


            //Chain 1
            MeshGeometry3D chain1Mesh = new MeshGeometry3D();//links, also -width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2 - chainWidth);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2 - chainWidth);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2);
            Point3D i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2);
            Point3D k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2);

            BuildRectangle(chain1Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain1Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain1Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain1Mesh, a,c,g,e, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain1Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain1Mesh, e,g,k,i, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain1Model = new GeometryModel3D(
            chain1Mesh,
            chainMaterial);


            //Chain 2
            MeshGeometry3D chain2Mesh = new MeshGeometry3D();//rechts, also +width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2 + chainWidth);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2 + chainWidth);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2);
            j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);
            k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2);
            l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);

            BuildRectangle(chain2Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain2Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain2Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain2Mesh, b, f, h, d, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain2Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain2Mesh, f, j, l, h, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain2Model = new GeometryModel3D(
            chain2Mesh,
            chainMaterial);


            //Build the whole object
            Model3DGroup model3DGroup = new Model3DGroup();
            model3DGroup.Children.Add(BodyModel);
            model3DGroup.Children.Add(Chain1Model);
            model3DGroup.Children.Add(Chain2Model);

            ModelVisual3D model = new ModelVisual3D();
            model.Content = model3DGroup;
            return model;
        }

        //Funktioniert auch mit nicht-Rechtecken, also wenn nicht alle Innenwinkel 90° sind.
        private void BuildRectangle(MeshGeometry3D geometry,Point3D a, Point3D b, Point3D c, Point3D d,Vector3D normal)
        {
            int baseIndex = geometry.Positions.Count;

            //Add vertices
            geometry.Positions.Add(a);
            geometry.Positions.Add(b);
            geometry.Positions.Add(c);
            geometry.Positions.Add(d);

            //Add normals
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);

            //Add indices
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 1);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 3);
        }
    }
}


And here the code for MainWindow.xaml:

<Window x:Class="Wpf3dTest1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewport3D Name="viewport3D">
            <Viewport3D.Camera>
                <PerspectiveCamera Position="20,10,10"
                                   LookDirection="-20,-10,-10"
                                   UpDirection="0,1,0"
                                   FieldOfView="45"
                                   NearPlaneDistance="1"
                                   FarPlaneDistance="100"></PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelUIElement3D>
                <AmbientLight Color="White"></AmbientLight>
            </ModelUIElement3D>
        </Viewport3D>
    </Grid>
</Window>

No comments:

Post a Comment