Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / WPF

WFTools3D: A Small WPF Library To Build 3D Simulations Very Easily

Rate me:
Please Sign up or sign in to vote.
5.00/5 (25 votes)
24 Mar 2016GPL311 min read 38.1K   2.3K   24   11
3D Tools for the Windows Presentation Foundation (WPF)

Image 1

Introduction

The Windows Presentation Foundation (WPF) comes with an easy-to-use 3D framework which is essentially a wrapper around DirectX. Although the performance is not as high as it would be when using Direct3D directly, it's good enough for simple scenarios, and it's definitely fun to play around with it.

The WFTools3D library makes using WPF 3D even more simple and more fun.

I started this project years ago to save myself from writing the same lines of code over and over again when building up a 3D scene for different kinds of simulations. For example, when creating a simulation of the solar system, it shouldn't take more than a few lines to set up the whole scene including the sun and the planets. The positions and rotation states of objects in the scene should be accessible with simple properties of the objects and last but not least I wanted to be able to fly through my scenes in the same way as I can fly around the world with a flight simulator. I love flight simulators!

Background

Microsoft's 3D Graphics Overview to WPF and the advanced How-To topics have all the information that one needs to embed 3D graphics in a Windows application. All you need is a Viewport3D, one or more ModelVisual3D, a camera and some lights. So far, so good. But it takes a rather long time until the first triangle appears on the screen! The WFTools3D library helps to shorten this time and allows for building up even complex scenes with e.g. moving cars and flying airplanes in some minutes. The scenes are interactive, i.e., you can move and rotate the built-in cameras with the keyboard and the mouse. Additionally, the cameras can move themselves, which means they can fly, and you can change their speed and yaw, pitch and roll angles just like in a flight simulator.

Using the Code

For a quick demo, we will build a simulation of the sun-earth-moon system. This is somewhat simpler than the demo project which you should also download, but perhaps later. For now, just create a WPF application called 'Demo' with Visual Studio 2010 or later. The .NET Framework version needs to be at least four. If the project is created, add a reference to WFTools3D.dll. In MainWindow.xaml replace the Grid element by a WFTools3D.Scene3D element like this:

XML
<Window x:Class="Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wft3d="WFTools3D"
        Title="MainWindow" WindowState="Maximized"
        FocusManager.FocusedElement="{Binding ElementName=scene}">
  <wft3d:Scene3D x:Name="scene"/>
</Window>

Note that I maximized the window and set the logical focus to the scene. Now replace the code in MainWindow.xaml.cs with this:

C#
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using WFTools3D;

namespace Demo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            scene.Models.Add(new AxisModel(10));

            Sphere sun = new Sphere(32);
            sun.DiffuseMaterial.Brush = Brushes.Goldenrod;
            scene.Models.Add(sun);

            scene.Camera.Position = new Point3D(25, -15, 8);
            scene.Camera.LookAtOrigin();
        }
    }
}

Enough for now! Although our mini solar system is not finished yet, we can build and start the application. You will see a yellow sphere in the middle of a black screen with a red, green and blue line coming out of it. The lines are showing the x, y and z coordinate axes of our world. X is red, Y is green and Z is blue (or (r,g,b) for (x,y,z)). Before talking about the code, try this:

Press the left mouse button and move the mouse. It looks like the underlying world is being rotated. A left-right movement of the mouse apparently rotates the world around its z axis, whereas an up-down movement seems to rotate the world about a horizontal axis in the middle of the screen. In fact, what's really happening is that the camera is moved to a new position and directed towards the origin.

The arrow keys and PgUp, PgDn will rotate the camera, but not move it. Moving is done with Ctrl plus the previous keys.

To start flying, press 'W'. Every 'W' will increase the speed, 'S' will decrease it and 'X' will stop the movement. In flying mode (speed != 0), left mouse dragging will change the flying direction. If you're lost in space, press the spacebar. This will turn the camera back to the origin.

But now let's talk about the code:

C#
scene.Models.Add(new AxisModel(10));

Class Scene3D has a Models property which is used to populate the scene with 3D objects. The first object added is a model of the coordinate axes, which helps in the beginning when you build up a scene. The axes will have a length of 10 and you might ask "10 what? Yards, meters, pixels?" The answer is "It doesn't matter." It's just 10 units and I will build up the scene with objects whose size and position will fit into a cube of size 10. Any other number like 1 or 1000 or 42 will be fine as well. You just have to size and position your models (and cameras) according to this number.

C#
Sphere sun = new Sphere(32);
sun.DiffuseMaterial.Brush = Brushes.Goldenrod;
scene.Models.Add(sun);

This adds a yellow sphere to the scene. Class Sphere is a Primitive3D which is an Object3D which is a ModelVisual3D which is the WPF base class for objects in a 3D scene. Class Object3D makes it easy to scale, rotate and position the object. For sure, this can also be done with the underlying ModelVisual3D class, but Object3D has properties like Position, ScaleX or Rotation1 which modify the Transform property of the base class more conveniently.

Class Primitive3D adds a mesh and materials to the object. The mesh describes the surface of the object and is made up of many (sometimes many, many) triangles. The materials (there is a front and a back material) specify how the surface looks like. The Material property (which represents the front material) in fact is a group of materials. You can access the individual items with properties DiffuseMaterial, SpecularMaterial and EmissiveMaterial. If the object is closed, you don't have to take care about the BackMaterial, otherwise you might want to code BackMaterial = Material if the object looks the same from all sides.

The sphere is a closed object, so we just have to take care about the front material. We can give it any color by setting the Brush of its DiffuseMaterial. Instead of a SolidColorBrush, we can also use an ImageBrush, which for sure is more appealing and is done in the demo project. Each Primitive3D has a constructor which takes an integer value called 'divisions' which determines the number of triangles used to create the mesh. If you would go with '1' instead of '32', the sphere would look like a double-pyramid as the total number of triangles is only 8.

C#
scene.Camera.Position = new Point3D(25, -15, 8);
scene.Camera.LookAtOrigin();

This is the last step in our 6 lines setup of the scene: we have to set the camera to a certain position and make it look at a certain point (which right now is the origin of the coordinate system). Class Scene3D has three perspective cameras and scene.Camera refers to the active camera. To activate another camera, use scene.ActivateCamera(int index).

The scene is illuminated by a default lighting model which is accessible by property Scene3D.Lighting. There are two directional lights called DirectionalLight1 and DirectionalLight2 and an ambient light called AmbientLight. The default model works OK for most of my scenarios.

But Where is the Earth?

Indeed our sun needs at least one companion! So let's add an earth to the scene. The code is pretty much the same as it is for adding the sun. Put the following code right after the sun has been added to the scene:

C#
Sphere earth = new Sphere(24) { Radius = 0.5, Position = new Point3D(9, 0, 0) };
earth.DiffuseMaterial.Brush = Brushes.Blue;
scene.Models.Add(earth);

The only real difference is its radius and position. The default position (0,0,0) and the default radius of 1 worked fine for the sun, but for the earth we have chosen different values. If you build and run the application, you'll now see the earth near the end of the red x axis.

And What About the Moon?

For the moon, we take a slightly different approach:

C#
Sphere moon = new Sphere { Radius = 0.3, Position = new Point3D(2, 0, 0) };
moon.DiffuseMaterial.Brush = Brushes.NavajoWhite;
earth.Children.Add(moon);

The important thing here is that the moon is not added to the models of the scene, but to the children of the earth (which is also a models collection). The reason for this is the fact that we want earth and moon to stay together. They make up their own system. Whenever the earth is positioned to a new location, we want the moon to follow the earth. If we decide to remove the earth, its moon should also be gone. And this is exactly what the Children property is meant for. Build and run the application and you'll see that now there is a moon at the end of the x axis.

Why does it appear at x = 10? The earth is located at x = 9, and the x position of the moon is 2. So where is the 10 coming from?

The reason is that since the moon is a child of the earth, its coordinate system is centered at the earth. So if we would put the moon at (0,0,0), it would be placed right in the middle of the earth (and we wouldn't see it anymore because it's smaller). To get the global position of the moon, we have to add its relative position (2,0,0) to its parent position (9,0,0). But that makes (11,0,0) - and the moon is definitely located at (10,0,0)! So how is that?

It's because we gave the earth a radius of 0.5! By scaling an Object3D, and setting the radius of a sphere is the same as setting its ScaleX, ScaleY and ScaleZ to the same value, we're not only scaling the geometry of the object but the whole world of this object. Even its coordinate system! So everything is shrunk to the half in the earth system. And that's why a distance of 2 in the earth system only means 1 in the earth's parent system (which is the global system right now). And that's why the moon is located at x = 10.

And Yet It Moves!

According to Galileo Galilei, the earth moves around the sun and the moon moves around the earth. We have no reason to not believe this wise man, so let's add a few lines of code to show some kind of movement. It will not be the real movement, but anyway it looks funny. At the end of the window constructor, add these two lines:

C#
scene.TimerTicked += TimerTicked;
scene.StartTimer();

Also, add a method for the TimerTicked event:

C#
void TimerTicked(object sender, EventArgs e)
{
    angle += 2;
    Object3D earth = scene.Models[2] as Object3D;
    earth.Rotation1 = Math3D.RotationZ(angle);
    earth.Rotation3 = Math3D.RotationZ(angle * 0.1);
}
double angle;

The above code starts a DispatcherTimer inside of the scene which fires every 30 milliseconds. When this happens, our TimerTicked() method gets called which just sets two Rotation properties of the earth. Rotation1 and the not used Rotation2 are applied with respect to the center of the earth, whereas Rotation3 uses the origin of the parent coordinate system as rotation center. So the first rotation makes the earth move around itself while the second one makes the earth move around the sun (with a lower speed).

Since the moon is a child of the earth, it follows the earth whatever she's doing. So if the earth is spinning around itself, the moon will also spin around the center of the earth. And for sure the moon follows mother earth on her way around the sun!

Flying Through Space!

Remember the 'W', 'S' and 'X' keys? Pick up some speed with 'W' and try to follow the earth! If you're too fast, press 'S' to get slower or press 'X' to stop completely. You will find that it's really hard to fly a given course! By the way, press 'H' for two times. The first keystroke will visualize the cameras and the second one will show a special kind of attitude director indicator (ADI) which helps to not lose orientation while flying. The stick with the red ball is always directed to the origin.

The cameras are visualized as airplanes. Look around and you will find two of them. You will not find another airplane, because that belongs to the camera that you're currently looking with! The tip of an airplane is showing the looking direction of the camera while its vertical stabilizer shows the camera's up direction. You can change the active camera with keys '1', '2' and '3'.

To finish this demo, we will set up the cameras in a funny way: the first one is fixed as it was before, the second one orbits the sun and the third one follows the earth. The complete code for the MainWindow now looks like this:

C#
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using WFTools3D;

namespace Demo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            scene.Models.Add(new AxisModel(10));

            Sphere sun = new Sphere(32);
            sun.DiffuseMaterial.Brush = Brushes.Goldenrod;
            scene.Models.Add(sun);

            Sphere earth = new Sphere(24) { Radius = 0.5, Position = new Point3D(9, 0, 0) };
            earth.DiffuseMaterial.Brush = Brushes.Blue;
            scene.Models.Add(earth);

            Sphere moon = new Sphere { Radius = 0.3, Position = new Point3D(2, 0, 0) };
            moon.DiffuseMaterial.Brush = Brushes.NavajoWhite;
            earth.Children.Add(moon);

            scene.ActivateCamera(2);
            scene.Camera.Position = new Point3D(9, 0, 0.1);
            scene.Camera.LookDirection = Math3D.UnitY;
            scene.Camera.UpDirection = Math3D.UnitZ;
            scene.Camera.Rotate(Math3D.UnitZ, -30);
            scene.Camera.ChangeRoll(-12);
            scene.Camera.Speed = 8;

            scene.ActivateCamera(1);
            scene.Camera.Position = new Point3D(0, 4, 0);
            scene.Camera.LookDirection = Math3D.UnitX;
            scene.Camera.UpDirection = Math3D.UnitZ;
            scene.Camera.ChangeRoll(25);
            scene.Camera.Speed = 8;

            scene.ActivateCamera(0);
            scene.Camera.Position = new Point3D(25, -15, 8);
            scene.Camera.LookAtOrigin();

            scene.ToggleHelperModels();
            scene.TimerTicked += TimerTicked;
            scene.StartTimer();
        }

        void TimerTicked(object sender, EventArgs e)
        {
            angle += 2;
            Object3D earth = scene.Models[2] as Object3D;
            earth.Rotation1 = Math3D.RotationZ(angle);
            earth.Rotation3 = Math3D.RotationZ(angle * 0.1);
        }
        double angle;
    }
}

You will notice that the airplanes are visible without pressing the 'H' key. That's done by a call to scene.ToggleHelperModels(). Press 'H' again to additionally show the ADI and again to remove both airplanes and the ADI. Here's a list of all keyboard/mouse commands:

  • 1, 2, 3: Activate camera 1, 2 or 3
  • W, S: Increase/decrease speed
  • X: Set speed to 0
  • T: Turn backwards
  • Space: Turn to origin
  • H: Toggle airplanes and ADI
  • Mouse Wheel: Increase/decrease field of view

If camera speed is 0:

  • LMB: Rotate scene about origin
  • Ctrl+LMB: Rotate scene about touchpoint
  • Arrows: Change look direction
  • PgUp, PgDn: Change roll angle
  • Ctrl+Arrows: Move camera left/right and forward/backward
  • Ctrl+PgUp, PgDn: Move camera up, down
  • Shift: Increase all above motion steps

If camera speed is not 0, i.e., in flying mode:

  • LMB, Arrows: Change pitch and roll angles
  • Ctrl+LMB: Change look direction
  • A, D: Fly standard turn left/right
  • F: Fly parallel to the ground

That's It!

I hope you enjoyed the tour. If you like to see more examples, check out my repositories on github. Project EquationOfTime has a very realistic sun-earth simulation which explains a surprising fact about sunrise and sunset times and project DoublePendulum inspects a double pendulum and its chaotic behaviour.

History

  • 19-Mar-2016: Initial upload

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior)
Germany Germany
I started programming in Basic, Pascal, Fortran and C in the late 1980s during my last semesters at the University of Bonn, Germany, where I studied Physics. As a professional software engineer I moved on to C++ and C# in the field of scientific data acquisition, data analysis and - my favourite - data visualization.

From the very start of my life as developer I have been a fan of graphics, especially 3D graphics. I have been working with OpenGL, XNA and WPF 3D. Planning to start with SharpDX in the near future.

Besides programming I love making music (guitar and violin), doing sports (rock climbing and volleyball) and spending time with my beloved family.

Comments and Discussions

 
Questionlovely Pin
Glen Wardrop7-Oct-21 2:16
Glen Wardrop7-Oct-21 2:16 
just what I've been looking for !
AnswerRe: lovely Pin
Wolfgang Foerster9-Oct-21 9:15
professionalWolfgang Foerster9-Oct-21 9:15 
QuestionSmall issue and the fix Pin
Dustin_0020-Dec-18 16:13
Dustin_0020-Dec-18 16:13 
QuestionThis is awesome! Pin
MarkWardell4-Dec-18 1:48
MarkWardell4-Dec-18 1:48 
QuestionGreat tool kit Pin
Member 344439321-Sep-17 2:55
Member 344439321-Sep-17 2:55 
GeneralMy vote of 5 Pin
webmaster44225-Mar-16 5:38
webmaster44225-Mar-16 5:38 
GeneralRe: My vote of 5 Pin
Wolfgang Foerster25-Mar-16 7:26
professionalWolfgang Foerster25-Mar-16 7:26 
Praisenice job, vote of 5 from me! Pin
Cryptonite21-Mar-16 8:46
Cryptonite21-Mar-16 8:46 
QuestionImage at the beginning of the article seems to be broken. Pin
Sven Bardos21-Mar-16 5:54
Sven Bardos21-Mar-16 5:54 
AnswerRe: Image at the beginning of the article seems to be broken. Pin
Wolfgang Foerster22-Mar-16 11:52
professionalWolfgang Foerster22-Mar-16 11:52 
GeneralRe: Image at the beginning of the article seems to be broken. Pin
Sven Bardos22-Mar-16 11:54
Sven Bardos22-Mar-16 11:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.