Playing with WPF in the project explorer

#1
This is an example of a preview of a flight simulator model inserted directly into a treeview using WPF. It is extremely trivial code, and was done in one evening. Obviously I already had the decoding of the MDL file done). Yes, I know the tree is ugly without icons etc, yes, I know the camera angle is not perfect. Yes, I know it is not antialised (and I have no idea if this can be changed under XP/Vista etc) - but it is a standard treeview, nothing custom whatsoever.

http://www.moellebjerg.com/FlightSimulator/ProjectExplorerPreview.PNG

The model is selected because it does not have any texture. Textures are supported in the WPF 3D models, but I am not aware of which texture formats it can load.

If you want to see the treeview in action, download this:
http://www.moellebjerg.com/FlightSimulator/ProjectExplorerPreview.zip

and install this before running the exe file:
http://www.microsoft.com/downloads/...90-26E6-44E0-8780-5D3CCD3D94ED&displaylang=en

The model will currently rotate initially. There is no way to restart it. I do not think the preview should rotate, but if you click the model and hold down the button you should probably be able to rotate manually.
 
#4
Just for the fun of it, I added support for controlling the rotation using the mouse. As I had to create a user control to support this, I tried adding a larget preview of a more complex model (still without textures, so it looks a bit strange - not to mention it is too big for the current camera angle).

While one could have hoped for better performance, I am still quite sure this is the best way to do the project solution tree and model preview, as it basically does not require any coding to do this. Speed should improve with Vista.

The download link from above has been updated in case someone wants to take a look.
 

arno

Administrator
Staff member
FSDevConf team
Resource contributor
#5
Hi Lars,

Looks good. How did you create that 3D preview? I expect if we use something like DirectX that it should perform a lot better. At least that is what I learned with MDL Tweaker, once I optimized the drawing code enough it does not take too much CPU.
 
#6
Hi Lars,

Looks good. How did you create that 3D preview? I expect if we use something like DirectX that it should perform a lot better. At least that is what I learned with MDL Tweaker, once I optimized the drawing code enough it does not take too much CPU.
It's a WPF standard 3D viewport, hence it works as a control even when splaced in the treeview. I do not see our speed requirements should warrent using something so lowlevel as DirectX to do such a standard task as displaying a 3D model, specifically not as the rumor is it will be hardware accelerated under Vista.

This is the code to do the preview. The XAML code sets up the viewport, camera, and light.
Code:
<UserControl x:Class="SampleProjectExplorer.Controls.FSModelPreviewControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Viewport3D MinWidth="64" MinHeight="64" Name="viewport">
    <Viewport3D.Camera>
      <PerspectiveCamera 
      FarPlaneDistance="500"
      LookDirection="-10,0,0"
      UpDirection="0,1,0"
      NearPlaneDistance="1" 
      Position="60,0,0" 
      FieldOfView="45" />
    </Viewport3D.Camera>
    <ModelVisual3D>
      <ModelVisual3D.Content>
        <DirectionalLight 
         Color="White" 
         Direction="-2,-3,-1" />
      </ModelVisual3D.Content>
    </ModelVisual3D>
  </Viewport3D>
</UserControl>
The code behind is simply rebuilding a XAML 3D object from my memory presentation. It is then handling mouse events to support the rotate. There is probably a smarter way to deal with event hookup than this.

Code:
using System;
using System.Collections.Generic;
using System.Text;
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.Navigation;
using System.Windows.Shapes;

using Bvl = Bevelstone.FlightSimulator;
using System.Windows.Media.Media3D;

namespace SampleProjectExplorer.Controls
{
    /// <summary>
    /// Interaction logic for FSModelPreviewControl.xaml
    /// </summary>

    public partial class FSModelPreviewControl : System.Windows.Controls.UserControl
    {
        public FSModelPreviewControl()
        {
            InitializeComponent();
            this.MouseDown += new MouseButtonEventHandler(OnMouseDown);
            this.MouseMove += new MouseEventHandler(OnMouseMove);
            this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
            viewport.Children.Add(model);
            Transform3DGroup transformGroup = new Transform3DGroup();
            transformGroup.Children.Add(new RotateTransform3D(rotateVertical));
            transformGroup.Children.Add(new RotateTransform3D(rotateHorizontal));

            model.Transform = transformGroup;
        }

        ModelVisual3D model = new ModelVisual3D();
        AxisAngleRotation3D rotateHorizontal = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 45);
        AxisAngleRotation3D rotateVertical = new AxisAngleRotation3D(new Vector3D(1, 0, 0), 45);


        Bvl.FlightSimModel fsModel = null;
        public Bvl.FlightSimModel FSModel
        {
            get
            {
                return fsModel;
            }
            set
            {
                model.Children.Clear();
                fsModel = value;
                
                if (fsModel != null)
                {
                    foreach (Bvl.TriangleSet ts in fsModel.LevelsOfDetail[0].TriangleSets)
                    {
                        MeshGeometry3D triangleMesh = new MeshGeometry3D();
                        foreach (Bvl.Triangle t in ts)
                        {
                            // Should reuse vertexes here - oh well, can't have it all in the first attempt :)
                            int index = triangleMesh.Positions.Count;
                            for (int i = 2; i >= 0; i--) // Need them in reverse order of what FS use... or maybe I just swap them when writing the BGL file :)
                            {
                                Bvl.Vertex v = t.Vertexes[i];
                                triangleMesh.Positions.Add(new Point3D(v.X, v.Y, v.Z));
                                triangleMesh.Normals.Add(new Vector3D(v.Nx, v.Ny, v.Nz));
                                triangleMesh.TriangleIndices.Add(triangleMesh.TriangleIndices.Count);
                            }
                        }
                        Color color = Color.FromScRgb(ts.Material.DiffuseA, ts.Material.DiffuseR, ts.Material.DiffuseG, ts.Material.DiffuseB);
                        Material material = new DiffuseMaterial(new SolidColorBrush(color));
                        GeometryModel3D triangleModel = new GeometryModel3D(triangleMesh, material);
                        ModelVisual3D subModel = new ModelVisual3D();
                        subModel.Content = triangleModel;
                        model.Children.Add(subModel);
                    }
                }
                
            }
        }


        bool capture = false;
        Point mouseDownPos;
        double startAngleX;
        double startAngleY;


        void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (capture)
            {
                double dx = e.GetPosition(this).X - mouseDownPos.X;
                dx = startAngleX + dx;
                rotateHorizontal.Angle = dx;
                double dy = e.GetPosition(this).Y - mouseDownPos.Y;
                dy = startAngleY + dy;
                if (dy > 90) dy = 90;
                if (dy < -90) dy = -90;
                rotateVertical.Angle = dy;
            }
        }

        void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                capture = true;
                mouseDownPos = e.GetPosition(this);
                startAngleX = rotateHorizontal.Angle;
                startAngleY = rotateVertical.Angle;
                CaptureMouse();
            }
        }

        void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (capture) ReleaseMouseCapture();

            capture = false;
        }


    }
}
 

arno

Administrator
Staff member
FSDevConf team
Resource contributor
#7
Hi Lars,

Thanks, I try to do some studying on those things (all new to me). We just don't want to go over our internal MDL file each time we need to redraw the screen. We probably want to store some drawlist (or whatever it is called), that we can just call to draw it. Without having to collect all triangles, etc from the model all the time. Once I did it like that in MDL Tweaker, the performance went up a lot.
 
#8
Hi Lars,

Thanks, I try to do some studying on those things (all new to me). We just don't want to go over our internal MDL file each time we need to redraw the screen. We probably want to store some drawlist (or whatever it is called), that we can just call to draw it. Without having to collect all triangles, etc from the model all the time. Once I did it like that in MDL Tweaker, the performance went up a lot.
Arno,

I think you are worrying on a bit too low abstraction level :)

The FSModel being set is already an in-memory representation of a 3D object (notice I wrote 3D object, not MDL file - an MDL file is just one possible source of the content of the 3D object). This means that there is an object structure consisting of LoD's, trinagle sets, transformations, lights etc. All as a domain model object, no low lever stuff here.

Once it is being set in the control, the 3D objects are being changed into the WPF 3D object model. They are fairly similar so it doesn't take much as you can see - basically looping over triangles adding them to the WPF object structure. The various trianglesets are simply added to the WPF Model object already set in the viewport, so nothing more needs to be done - no repainting or other obsolete coding style - I already told the framework I want to display the model, so when I make changes the view on the screen is obviously updated.

For rotation all I have to do is change the angles in the two rotation transforms. As they are already set on the model, and the model is already set in the viewport on the form the screen will obviously be updated without me having to write the trivial code to do so.
 

arno

Administrator
Staff member
FSDevConf team
Resource contributor
#9
For rotation all I have to do is change the angles in the two rotation transforms. As they are already set on the model, and the model is already set in the viewport on the form the screen will obviously be updated without me having to write the trivial code to do so.
Then I wonder why it takes 100% CPU to rotate such a simple object. That should be possible more efficient I would say.
 
#10
Then I wonder why it takes 100% CPU to rotate such a simple object. That should be possible more efficient I would say.
It is not hardware accelerated under legacy operating systems, which by now include XP. :)

I haven't bothered installing Vista though, so I can't say how much better it is.
 

arno

Administrator
Staff member
FSDevConf team
Resource contributor
#11
It is not hardware accelerated under legacy operating systems, which by now include XP. :)

I haven't bothered installing Vista though, so I can't say how much better it is.
Would that not make it a bad choice for us at the moment then? I think a lot of people will be using XP with our tool. I might install Vista and see how it looks there.
 
#12
Would that not make it a bad choice for us at the moment then? I think a lot of people will be using XP with our tool. I might install Vista and see how it looks there.
It is workable under XP (how fast do you really need to be to update your model preview). :)

The programming benefits from using WPF are not what I would call big.... I would call them gigantic. We are not going to program it all in assembler to make sure it runs as fast as possible either are we? :)
 

scruffyduck

Administrator
Staff member
FSDevConf team
Resource contributor
#13
Hi Lars

Looking at the code for this you are using a namespace called Bevelstone to generate the 3D model - is that a library you created to handle this kind of thing?
 
#14
Hi Lars

Looking at the code for this you are using a namespace called Bevelstone to generate the 3D model - is that a library you created to handle this kind of thing?
Yes, it contains the domain model of a 3D object. Unfortunately it does not support everything in FS (lacking animations and attachpoints), but it does give an object representation of Level of Detail, trianglesets, and lights with the corresponding texture and material.

It decompiles MDL and FS2002 BGL files (streight GMax export) as long as it does not encounter any nasty animations :)

It can to transformations of the model as well (what Arnout call Tweaks).
 
Top