Click here to Skip to main content
Email Password   helpLost your password?

Introduction

.NET 3.5 introduced a few new langauge features, oh and there was that little thing called LINQ. But with all the excitement, sometimes a few things get missed. Such as the new 3D elements that are available to WPF developers in .NET 3.5. This article will discuss the use of some of these new 3D related elements that are now in .NET 3.5.

In order to demonstrate some (yeah I dont use all of the new elements in the attached demo app) I picked something simple. I decided to have a bunch of 3D meshes within a Viewport3D (3d scene) that could be clicked on to open blog entries. I allow users to switch to 3 different blogs, my own, Josh Smiths and Karl Shiffletts

Here is what this article contains :

A Video Of The Demo App

Due to the nature of 3D, the only way that I can do the attached demo application any justice, is to show you a video, which shows it in action. As such please click on the image below to see a video of the demo application in action

Click the image or here to view the video

I would reccommend waiting until the entire video has streamed then watch it

The New .NET 3.5 Elements

There are 3 new 3D related elements in .NET 3.5, each of which is described below. But in order to understand why these new elements are so cool, you need to go back to .NET 3.0 land. In .NET 3.0, ModelVisual3D elements, were simple elements, and these ModelVisual3D elements did not support the routed events handling capabilities offered by their 2D counterparts. So when working with 3D elements and wanting to respond to some mouse events or perform hit testing, you had to do it manually. Now this sounds ok, but the only place you could actually carry out hit testing or even listen to routed events, was actually on the Viewport3D (3d scene) that contained the various ModelVisual3D elements (3d objects).

This worked something like this:

private void viewport_MouseDown(object sender, MouseButtonEventArgs e)
{
    Viewport3D viewport = (Viewport3D)sender;
    Point location = e.GetPosition(viewport);
    HitTestResult hitResult = VisualTreeHelper.HitTest(viewport, location);
    
    if (hitResult != null && hitResult.VisualHit == SOME_VISUAL)
    {
        // Hit the visual.
    }
    

    RayMeshGeometry3DHitTestResult meshHitResult = hitResult as RayMeshGeometry3DHitTestResult;
    if (meshHitResult != null && meshHitResult.ModelHit == SOME_MODEL)
    {
        // Hit the model.
    }
    if (meshHitResult != null && meshHitResult.MeshHit == SOME_MESH)
    {                

        //do something
               
    }
}

Now this isn't that bad, for one ModelVisual3D element (3d object). But if you had loads this just isn't that fun. This is why the new ContainerUIElement3D /ContainerUIElement3D elements are cool. They are proper full blown elements, that support input, focus, and events. So we don't have to do code like the above any more, we simply wire up a routed event handler and write the appropriate code in code behind. Much better. Lets have a look at these new elements in a bit more detail.

ContainerUIElement3D

ContainerUIElement3D simply provides a container for ModelUIElement3D objects. ContainerUIElement3D is a ModelUIElement3D object, which provides support for input, focus, and events in 3-D. Stealing from MSDN, this is an example with 2 cube ModelUIElement3D elements within a ContainerUIElement3D. Notice the use of the ContainerUIElement3D MouseDown routed event and ModelUIElement3D MouseDown routed events. Fully fledged events on 3d objects. Cool.

<Viewport3D>
    <Viewport3D.Camera>
        <PerspectiveCamera Position="8,3,0" LookDirection="-8,-3,0" />
    </Viewport3D.Camera>

    <!-- The container has the two cubes as its children -->
    <ContainerUIElement3D MouseDown="ContainerMouseDown">
        <ContainerUIElement3D.Transform>
            <RotateTransform3D>
                <RotateTransform3D.Rotation>
                    <AxisAngleRotation3D x:Name="containerRotation" Axis="0, 1, 0" Angle="0" />
                </RotateTransform3D.Rotation>
            </RotateTransform3D>
        </ContainerUIElement3D.Transform>

        <!-- Cube 1 -->
        <ModelUIElement3D MouseDown="Cube1MouseDown">
            <ModelUIElement3D.Transform>
                <TranslateTransform3D OffsetZ="1.5" />
            </ModelUIElement3D.Transform>

            <ModelUIElement3D.Model>
                <GeometryModel3D Geometry="{StaticResource CubeMesh}">
                    <GeometryModel3D.Material>
                        <DiffuseMaterial x:Name="cube1Material" Brush="Blue" />
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelUIElement3D.Model>
        </ModelUIElement3D>

        <!-- Cube 2 -->
        <ModelUIElement3D MouseDown="Cube2MouseDown">
            <ModelUIElement3D.Transform>
                <TranslateTransform3D OffsetZ="-1.5" />
            </ModelUIElement3D.Transform>

            <ModelUIElement3D.Model>
                <GeometryModel3D Geometry="{StaticResource CubeMesh}">
                    <GeometryModel3D.Material>
                        <DiffuseMaterial x:Name="cube2Material" Brush="Green" />
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelUIElement3D.Model>
        </ModelUIElement3D>                
    </ContainerUIElement3D>

    <!-- Lights -->
    <ModelVisual3D>
        <ModelVisual3D.Content>
            <PointLight Color="White" Position="3, 10, 4" />
        </ModelVisual3D.Content>
    </ModelVisual3D>
</Viewport3D>

ModelUIElement3D

As previously stated these are new elements that provide rendering of a 3-D model, that supports input, focus, and events. Using the same example as before, noticing the ModelUIElement3D MouseDown routed event :

        <!-- Cube 2 -->
        <ModelUIElement3D MouseDown="Cube2MouseDown">
            <ModelUIElement3D.Transform>
                <TranslateTransform3D OffsetZ="-1.5" />
            </ModelUIElement3D.Transform>

            <ModelUIElement3D.Model>
                <GeometryModel3D Geometry="{StaticResource CubeMesh}">
                    <GeometryModel3D.Material>
                        <DiffuseMaterial x:Name="cube2Material" Brush="Green" />
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelUIElement3D.Model>
        </ModelUIElement3D>                
    </ContainerUIElement3D>

Viewport2DVisual3D

Although the demo application doesn't actually use this, I can say a few words about this new element. Basically the Viewport2DVisual3D element is an element that can be used within a Viewport3D (3d scene), but hosts an interactive 2d Element. Such as a Button. I've taken the following example code straight from MSDN. The following example shows how to place a button, a 2-D object, on a 3-D object. Note that you must set the IsVisualHostMaterial attached property on the material on which you wish to have the 2-D visual placed.

  <Viewport3D>

    <!-- Button on 3D -->
    <Viewport2DVisual3D>
        <!-- Give the plane a slight rotation -->
        <Viewport2DVisual3D.Transform>
            <RotateTransform3D>
                <RotateTransform3D.Rotation>
                    <AxisAngleRotation3D Angle="40" Axis="0, 1, 0" />
                </RotateTransform3D.Rotation>
            </RotateTransform3D>
        </Viewport2DVisual3D.Transform>

        <!-- The Geometry, Material, and Visual for the Viewport2DVisual3D -->
        <Viewport2DVisual3D.Geometry>
            <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0"
                            TextureCoordinates="0,0 0,1 1,1 1,0" 
                            TriangleIndices="0 1 2 0 2 3"/>
        </Viewport2DVisual3D.Geometry>

        <Viewport2DVisual3D.Material>
            <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" 
            Brush="White"/>
        </Viewport2DVisual3D.Material>                               

        <Button>Hello, 3D</Button>
    </Viewport2DVisual3D>

</Viewport3D>
  

Demo App

So what does the attached demo app actually do. Well it looks like the following diagram when it first starts

Where a number of 3d objects (ModelUIElement3D elements) are shown to represent blog entries. Moving the mouse over one of these individual ModelUIElement3D elements causes them to grow in scale, and when the mouse is moved out they shrink back to their original size.

This is achieved as follows:

<Tools:TrackballDecorator  >
    
    <Viewport3D>
        
        <Viewport3D.Camera>
            <PerspectiveCamera x:Name="camera" Position="-2,2,40" 
            LookDirection="2,-2,-40" FieldOfView="90" />
        </Viewport3D.Camera>

        <ContainerUIElement3D x:Name="container" />

        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
        
    </Viewport3D>
</Tools:TrackballDecorator>

So this sets up the Viewport3D (3d scene), and does the normal 3d stuff like create a Light and a Camera, but it also sets up a ContainerUIElement3D (container to host other ModelUIElement3D elements).

The only other thing to note here is that I am using a TrackballDecorator decorator element. This is not actually part of the .NET 3.0/3.5 frameworks, but is part of a Dll that the WPF 3D team released called 3dTools. Which is an open source codeplex project which is located right here.

This TrackballDecorator decorator element, allows the user to pan and scroll around the entire Viewport3D (3d scene) in 3D space. Its very cool.

So in the code behind a number of ModelUIElement3D elements are added which represent blog entries, where each ModelUIElement3D element uses a tesselate (sphere in my case) MeshGeometry3D mesh. This is done something like this.

this.Resources.Add("sphereMesh", Tesselate.Create(10, 10, 5));
.....
.....
ModelUIElement3D sphere1 = CreateSphere(brushes[1], points3D[0].X, points3D[0].Y, points3D[0].Z);
container.Children.Add(sphere1);
feedsForShapes.Add(sphere1, feeds[0]);
.....
.....
private ModelUIElement3D CreateSphere(Brush materialBrush, double OffsetX,
    double OffsetY, double OffsetZ)
{

    ModelUIElement3D sphere3D = new ModelUIElement3D();
    sphere3D.MouseEnter += new MouseEventHandler(Sphere_MouseEnter);
    sphere3D.MouseLeave += new MouseEventHandler(Sphere_MouseLeave);
    sphere3D.MouseDown += new MouseButtonEventHandler(sphere3D_MouseDown);
    GeometryModel3D sphere3D_Geom = new GeometryModel3D(
        this.Resources["sphereMesh"] as MeshGeometry3D,
        new DiffuseMaterial(materialBrush));
    sphere3D.Model = sphere3D_Geom;
    Transform3DGroup transGroup = new Transform3DGroup();
    ScaleTransform3D scaleTrans = new ScaleTransform3D(1, 1, 1);
    TranslateTransform3D translateTrans = 
        new TranslateTransform3D(OffsetX, OffsetY, OffsetZ);
    RotateTransform3D rotateTrans = new RotateTransform3D();
    rotateTrans.Rotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 1);
    transGroup.Children.Add(scaleTrans);
    transGroup.Children.Add(translateTrans);
    transGroup.Children.Add(rotateTrans);
    sphere3D.Transform = transGroup;

    return sphere3D;
}

The actual blog entries are read using a very simple bit of XLINQ, which is as follows:

public List GetFeedEntries(FeedMember feedMember)
{
    try
    {
        XElement feeds = XElement.Load(GetFeedUrl(feedMember));
        //check that there is some feed entries
        if (feeds.Element("channel") != null)
        {
            var items = (from f in feeds.Element("channel").Elements("item")
                         select new FeedEntry
                         {
                             Link = f.Element("link").Value,
                             Title = f.Element("title").Value
                             
                         }).Take(10);
            return items.ToList();
        }
        else
        {
            return null;
        }

    }
    catch(Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message + "\r\n");
        return null;
    }
}

The next section talks a little bit more about how the tesselates are created.

Tesselate Creation

Within WPF there are no standard meshes that can be used as GeometryModel3D properties. Some times there are example meshes for example directX has a well known teapot. No such luck in WPF. You have to do it manually. The following code shows how you can create a simple square MeshGeometry3D mesh:

<!-- Simple flat, square surface -->
<GeometryModel3D.Geometry>
    <MeshGeometry3D
         TriangleIndices="0,1,2    2,3,0"
         TextureCoordinates="0,1 1,1 1,0 0,0"
         Positions="-0.5,-0.5,0     0.5,-0.5,0    0.5,0.5,0    -0.5,0.5,0" />
</GeometryModel3D.Geometry>

Perhaps this could do with a little explanation. The Positions property is the positions in 3D space X,Y,Z planes. So we can see if this were mapped out we would get something like

And the TriangleIndices property is the indices of the triangles that make up the GeometryModel3D.Geometry, in this case a simple square, which is made from 2 seperate triangles. This is how 3D works. Lets see these 2 triangles (basically triangles are the building blocks of any 3d mesh) :

But what I wanted for this article was some nice sphere. Or to be more precise a tessalate.

Tesselation : A tessellation or tiling of the plane is a collection of plane figures that fills the plane with no overlaps and no gaps. One may also speak of tessellations of the parts of the plane or of other surfaces.

http://en.wikipedia.org/wiki/Tesselate

But what does that really mean in terms of a 3D mesh. Well consider the following image

We can see that dividing the sphere into divisions both around (theta) and up from the bottom pole to the top pole (phi), we can create rectangles. And we can treat each rectangle as we did with the above simple square MeshGeometry3D. So this would look something like the following figure. So we can see the triangles that are forming our mesh.

There is a helper class called tesselate.cs in the attached demo project which produces the appropriate MeshGeometry3D, here is the main part of that class:

/// <summary>
/// Tessellates the sphere and returns a MeshGeometry3D representing the 
/// tessellation based on the given parameters
/// </summary>
/// <param name="tDiv">theta Divisions</param>
/// <param name="pDiv">phi divisions</param>
/// <param name="radius">radius</param>
public static MeshGeometry3D Create(int tDiv, int pDiv, double radius)
{            
    double dt = DegToRad(360.0) / tDiv;
    double dp = DegToRad(180.0) / pDiv;

    MeshGeometry3D mesh = new MeshGeometry3D();

    for (int pi = 0; pi <= pDiv; pi++)
    {
        double phi = pi * dp;

        for (int ti = 0; ti <= tDiv; ti++)
        {
            // we want to start the mesh on the x axis
            double theta = ti * dt;

            mesh.Positions.Add(GetPosition(theta, phi, radius));
            mesh.Normals.Add(GetNormal(theta, phi));
            mesh.TextureCoordinates.Add(GetTextureCoordinate(theta, phi));
        }
    }

    for (int pi = 0; pi < pDiv; pi++)
    {
        for (int ti = 0; ti < tDiv; ti++)
        {
            int x0 = ti;
            int x1 = (ti + 1);
            int y0 = pi * (tDiv + 1);
            int y1 = (pi + 1) * (tDiv + 1);

            mesh.TriangleIndices.Add(x0 + y0);
            mesh.TriangleIndices.Add(x0 + y1);
            mesh.TriangleIndices.Add(x1 + y0);

            mesh.TriangleIndices.Add(x1 + y0);
            mesh.TriangleIndices.Add(x0 + y1);
            mesh.TriangleIndices.Add(x1 + y1);
        }
    }

    mesh.Freeze();
    return mesh;
}

Cant remember exactly where this code came from, but it was than likely the WPF 3D teams blog http://blogs.msdn.com/wpf3d/

History

27/03/08 : Initial release

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralLink to video Broken
daveauld
23:03 27 Dec '09  
Hi Sacha,

Subject says it all!


P.S. just come off of your website, and there are also various links back to your CP articles broken.

Cheers,


GeneralRe: Link to video Broken
Sacha Barber
23:57 27 Dec '09  
My blog is getting updated, and the problem is that when one of your unedited articles get edited (popular articles get edited when the get popular enough) the Url changes.

Too be honest I am too lazy er too busy writing new articles to fix things like links, I am very hyper active and constantly looking for next idea.

Old stuff rarely gets looked at again, sorry to say.

You know how it is right?

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Link to video Broken
daveauld
0:28 31 Dec '09  
Sacha Barber wrote:
I am very hyper active

I'm the same........the difference is i don't know how to do 95% of the things i want to do. you seem to know everything.

You are the chuck norris of the programming world!

Happy New Year!


GeneralRe: Link to video Broken
Sacha Barber
1:18 31 Dec '09  
Nah I am still a spring chicken, loads to still learn how to do.

I know nothing really.

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralNice Article
Razan Paul (Raju)
0:49 25 Mar '09  
How did you compute what should be the light position?
GeneralRe: Nice Article
Sacha Barber
3:10 25 Mar '09  
There is an bit of maths, I got it from the McDonald WPF book. Or you could just use Blender or Expression blend and move the camera with the camera tool.

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralDo you have example to translate/pan 3D?
wmjcomcn
20:07 6 Jul '08  
Hi, do you have example to translate/pan 3D? Please give me some suggestions: wmjcom@163.com. Thanks.
GeneralRe: Do you have example to translate/pan 3D?
Sacha Barber
11:26 7 Jul '08  
I dont but someone else here did something for that, you will have to search CP for it

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Do you have example to translate/pan 3D?
wmjcomcn
19:36 7 Jul '08  
I can't find pan example from net. If you have time, please make a example. It would be useful.
GeneralRe: Do you have example to translate/pan 3D?
Sacha Barber
9:47 8 Jul '08  
I saw it some time ago, id have to google for it, same as you.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Do you have example to translate/pan 3D?
wmjcomcn
21:51 10 Jul '08  
When I pan 3D using TranslateTransform3D, 3D graph has a little rotation. I don't know what the reason is. Can you make a test to pan your 3D? It is a little difficult. Thanks.
GeneralRe: Do you have example to translate/pan 3D?
Sacha Barber
22:30 10 Jul '08  
Your will have to speak to the folk at Microsoft , 3Dtools is done by them

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralVery sexy!
Member 1356646
21:10 2 Jun '08  
Forget LINQ, this is the true reason 3.5 is a ripper!

Big Grin
GeneralRe: Very sexy!
Sacha Barber
22:47 2 Jun '08  
Forget part1 part 2 is so much better

http://www.codeproject.com/KB/WPF/BeginWPF2.aspx
Im also doing part3 slowly now also, as I am also doing a series on threading

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralWow I missed it
Rama Krishna Vavilala
5:52 14 Apr '08  
These days I am too much pre-occupied with Mac and iPhone development that I forgot to notice that you have succumbed to my requests and started this series.

Jig Jig

You have, what I would term, a very formal turn of phrase not seen in these isles since the old King passed from this world to the next. martin_hughes on VDK

GeneralRe: Wow I missed it
Sacha Barber
21:47 14 Apr '08  
Thanks Rama.

If you like this one, you must check out the other one. Its the best thing I've done I think. Make sure to read the article though, as its got some rules you MUST abide by.

http://www.codeproject.com/KB/WPF/WPF3D_2.aspx

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Wow I missed it
Sacha Barber
3:05 10 Jun '08  
Here is a new one for you Rama

http://www.codeproject.com/KB/WPF/MarsaX.aspx

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Wow I missed it
Rama Krishna Vavilala
4:32 10 Jun '08  
NeatSmile

Better than the Amazon 3D (which in itself was great).

This has been discussed, again and again and again and always we (the denizens of the CP lounge) have come to the conclusion that their method of rating is pure, untouched, unadulterated, genuine, verifiable, refined trash. MIM on TIOBE

GeneralRe: Wow I missed it
Sacha Barber
4:51 10 Jun '08  
Thanks Rama, we liked that one

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

Generalnice but ...
Icebreaker_
3:52 2 Apr '08  
You could do the same thing with OpenGL with less code in less time than messing with that ugly XML stuff also it could be cross-platform Smile

These days people should concentrate on developing cross platform stuff rather than learning something proprietary Smile IMHO!

Other than that, really impressive Smile
GeneralRe: nice but ...
Sacha Barber
4:09 2 Apr '08  
Its not for me to say if this is better or worse than OpenGl, im just showing you how to do this stuff in WPF

Smile

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same view, and never argue

My Blog : sachabarber.net

GeneralWhy not show Viewport2DVisual3D in your demo app?
Dustin Metzgar
16:58 31 Mar '08  
I think 3D by itself has limited uses in real apps, but a mixture of 2D and 3D has real potential[^].

“Ooh... A lesson in not changing history from Mr. I'm-my-own-grandpa” - Prof. Farnsworth

GeneralRe: Why not show Viewport2DVisual3D in your demo app?
Sacha Barber
22:28 31 Mar '08  
Yes I know Lesters work well..we are actually part of the same blog group

http://wpfdisciples.wordpress.com/

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same view, and never argue

My Blog : sachabarber.net

GeneralVery nice
Daniel Vaughan
2:15 29 Mar '08  
Very nice Sacha,
I don’t know how you manage to produce so many good articles! You're a legend.

Daniel Vaughan

Blog: DanielVaughan.Orpius.com


GeneralRe: Very nice
Daniel Vaughan
2:25 29 Mar '08  
One thing... I would have liked to have heard some narration with the demo clip, or maybe even just some funky music.

Again, great work Sacha.

Daniel Vaughan

Blog: DanielVaughan.Orpius.com



Last Updated 27 Mar 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010