Chapter 10. Working with 3D Graphics

3D graphics programming is often a daunting task and can get quite hairy with all those vectors, matrices, and quaternion math, not to mention hit testing and texture mapping. Luckily, WPF provides a rich set of classes to help simplify and speed up the use of 3D in your applications. On the downside, though, you may still need to get your hands dirty with polygons to define the 3D content you want to display, such as a 3D model.

This chapter will be of most value to those who have at least a basic understanding of 3D coordinate spaces and who are familiar with mathematical concepts such as points (a position in space given as an offset from the coordinate system's origin), vectors (a direction and a magnitude), and matrices (a table of values).

Although 3D can take your user interfaces to the next level, it should be used sparingly and only where it will add value to your application. Using too much may also slow your application down on some older machines.

It is important to note that the WPF 3D graphics engine does not work like a ray-tracer where light values are calculated on a per-pixel basis, because this is very costly. Instead, the light is calculated for each vertex of a triangle and then interpolated to color the remainder of the triangle's surface. This means that although the output will have a realistic look, it won't be able to achieve the same level of realism possible with a ray-tracer. Despite this, you are still able to quickly and easily build fully interactive, 3D content right in to your application.

The recipes in this chapter describe how to:

Use 3D in Your Application

Problem

You need to display some 3D content in your application, be it a simple control or a complex 3D model.

Solution

Use a System.Windows.Controls.Viewport3D control to display 3D content in your 2D application.

How It Works

The Viewport3D control is a 2D control that hosts 3D content, rendering (or projecting) the content on to its 2D surface, much like 3D objects around us are projected onto the 2D surface of a camera's viewfinder. Like the viewfinder on a standard camera displays whatever the camera is looking at, the content displayed in a Viewport3D control is directed by a System.Windows. Media.Media3D.Camera implementation (see recipe 10-2 for more information about the Camera class).

The content of a Viewport3D is set through its Children property, a System.Windows.Media. Media3D.Visual3DCollection. The Visual3DCollection is a collection of objects implementing the abstract System.Windows.Media.Media3D.Visual3D class, which currently includes System. Windows.Media.Media3D.ModelVisual3D, System.Windows.Media.Media3D.Viewport2DVisual3D (see recipe 10-8), and System.Windows.UIElement objects.

Viewport3D derives from System.Windows.FrameworkElement so provides support for user input and focus, as well as methods for performing hit tests within the control, a very useful feature indeed. If something a little lighter is needed, the System.Windows.Media.Media3D. Viewport3DVisual is available. This derives from System.Windows.Visual, as opposed to Viewport3D, which derives from FrameworkElement. When using a Viewport3DVisual, you still get support for hit testing, but you lose built-in user input handling. A Viewport3DVisual is good to use when displaying 3D content within a 2D control and when the view is likely to be printed.

The Code

The following XAML demonstrates how to use the Viewport3D element in a simple scenario of displaying a single polygon (see Figure 10-1):

<Window
  x:Class="Recipe_10_01.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Recipe_10_01" Height="400" Width="600">
  <Viewport3D>
    <Viewport3D.Camera>
      <PerspectiveCamera
LookDirection="0,0,-1"
      Position="0,0,5" />
    </Viewport3D.Camera>
    <ModelVisual3D>
      <ModelVisual3D.Content>
        <AmbientLight Color="White" />
      </ModelVisual3D.Content>
    </ModelVisual3D>
    <ModelVisual3D>
      <ModelVisual3D.Content>
        <GeometryModel3D>
          <GeometryModel3D.Geometry>
            <MeshGeometry3D
              Positions="-1,-1,0 1,-1,0 1,1,0"
              TriangleIndices="0 1 2" />
          </GeometryModel3D.Geometry>
          <GeometryModel3D.Material>
            <DiffuseMaterial Brush="Firebrick" />
          </GeometryModel3D.Material>
        </GeometryModel3D>
      </ModelVisual3D.Content>
    </ModelVisual3D>
  </Viewport3D>
</Window>
A simple demonstration of rendering content in a Viewport3D control

Figure 10-1. A simple demonstration of rendering content in a Viewport3D control

Use a 3D Camera

Problem

You need to be able to control and alter the characteristics of the view in a System.Windows. Controls.Viewport3D, as well as choose the type of projection method used to render your 3D scene.

Solution

Use an implementation of the System.Windows.Media.Media3D.Camera class, defining the location, view direction, field of view, and so on, for the camera.

How It Works

Cameras are an important part of 3D graphics and control how the scene appears to the viewer when it is projected on to the image plane of the camera. This is just like the camera on a movie set that defines what the user sees on the screen in the theater.

The area of 3D space that is visible to the camera is inferred from the camera's configuration such as its location, direction it is looking, orientation, field of view, focal length near plane distance, and far plane distance. This area of space is known as the frustum; see http://en.wikipedia.org/wiki/Viewing_frustum for more information about a view frustum.

The camera's other function is to create a view matrix, a matrix that defines how objects in the world should be transformed so that the scene appears as expected from the given view. This view matrix also contains a projection matrix, which defines how points should be transformed so that they appear according to the camera's projection type, either perspective or orthographic. WPF provides both support for both of these types of camera in the System. Windows.Media.Media3D.PerspectiveCamera and System.Windows.Media.Media3D.OrthographicProjection camera..

The choice of projection method will depend on how you want your 3D scene to appear. When using a perspective projection camera, parallel lines will converge giving the perception of depth, or perspective. This type of projection gives more realistic projections, with objects appearing as they do in real life. When using orthographic projection, lines that are parallel remain parallel and never converge. This type of projection is ideally suited to computer-aided design (CAD) packages, where measurements need to be accurate. Figure 10-2 shows the difference between a view rendered using a PerspectiveCamera and an OrthographicProjection camera.

There is a third type of camera, the MatrixCamera, that allows a great deal of control over the way the camera constructs its view matrix. It does also mean that you need to do a great deal of work to get the camera functioning properly. By specifying a view matrix for the camera, you can define how your objects appear. For example, you may want to create a camera that simulates a fish-eye lens.

The Code

The following XAML demonstrates how to use Camera objects and how they can affect the way in which rendered objects appear. Of the two Viewport3D controls defined in the code, the first uses an OrthographicCamera, and the second uses a PerspectiveCamera. Both Viewport3D controls contain three System.Windows.Media.Media3D.ModelVisual3D objects, each defining a square of a different orientation and color (see Figure 10-2).

<Window x:Class="Recipe_10_02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Recipe_10_02" Height="300" Width="300">
  <Window.Resources>
    <!-- Front, left square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontLeft"
      Positions="-1,0,1 1,0,1 1,1,1 −1,1,1"
      TriangleIndices="0 1 2 0 2 3" />
    <!-- Front, right square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontRight"
      Positions="1,0,1 1,0,-1 1,1,-1 1,1,1"
      TriangleIndices="0 1 2 0 2 3" />
    <!-- Top square -->
    <MeshGeometry3D
      x:Key="squareMeshTop"
      Positions="-1,1,1 1,1,1 1,1,-1 −1,1,-1"
      TriangleIndices="0 1 2 0 2 3" />


    <DiffuseMaterial x:Key="diffuseFrontLeft" Brush="Firebrick" />
    <DiffuseMaterial x:Key="diffuseFrontRight" Brush="CornflowerBlue" />
    <DiffuseMaterial x:Key="diffuseTop" Brush="OrangeRed" />
  </Window.Resources>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="0.5*"/>
      <ColumnDefinition Width="0.5*"/>
    </Grid.ColumnDefinitions>


    <DockPanel>
      <TextBlock
      Text="Orthographic Projection"
      DockPanel.Dock="Bottom"
      HorizontalAlignment="Center" />
    <Viewport3D x:Name="OrthographicView">
      <Viewport3D.Camera>
        <OrthographicCamera
          Width="4"
         Position="10,10,10"
         LookDirection="-1,-1,-1"
         UpDirection="0,1,0" />
     </Viewport3D.Camera>
<!--Front left side-->
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <GeometryModel3D
         Geometry="{StaticResource squareMeshFrontLeft}"
         Material="{StaticResource diffuseFrontLeft}" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
     <!-- Front right side -->
       <ModelVisual3D>
         <ModelVisual3D.Content>
           <GeometryModel3D
             Geometry="{StaticResource squareMeshFrontRight}"
             Material="{StaticResource diffuseFrontRight}" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
     <!-- Top side -->
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <GeometryModel3D
         Geometry="{StaticResource squareMeshTop}"
         Material="{StaticResource diffuseTop}" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <AmbientLight Color="White" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
   </Viewport3D>
     </DockPanel>


   <DockPanel Grid.Column="1">
     <TextBlock
       Text="Perspective Projection"
       DockPanel.Dock="Bottom"
       HorizontalAlignment="Center" />
   <Viewport3D x:Name="PerpesctiveView" Grid.Column="1">
     <Viewport3D.Camera>
       <PerspectiveCamera Position="3,3,3" LookDirection="-1,-1,-1" />
     </Viewport3D.Camera>
       <!--Front left side-->
       <ModelVisual3D>
         <ModelVisual3D.Content>
         <GeometryModel3D
           Geometry="{StaticResource squareMeshFrontLeft}"
Material="{StaticResource diffuseFrontLeft}" />
           </ModelVisual3D.Content>
       </ModelVisual3D>
       <!-- Front right side -->
       <ModelVisual3D>
         <ModelVisual3D.Content>
         <GeometryModel3D
           Geometry="{StaticResource squareMeshFrontRight}"
           Material="{StaticResource diffuseFrontRight}" />
           </ModelVisual3D.Content>
       </ModelVisual3D>
       <!-- Top side -->
       <ModelVisual3D>
         <ModelVisual3D.Content>
         <GeometryModel3D
           Geometry="{StaticResource squareMeshTop}"
           Material="{StaticResource diffuseTop}" />
           </ModelVisual3D.Content>
       </ModelVisual3D>


       <ModelVisual3D>
         <ModelVisual3D.Content>
           <AmbientLight Color="White" />
         </ModelVisual3D.Content>
       </ModelVisual3D>
     </Viewport3D>
       </DockPanel>
   </Grid>
</Window>
Demonstrates the differences between orthographic and perspective projection

Figure 10-2. Demonstrates the differences between orthographic and perspective projection

Draw a 3D Model

Problem

You need to render a 3D model or shape within a System.Windows.Controls.Viewport3D control.

Solution

Use a System.Windows.Media.Media3D.ModelVisual3D or a System.Windows.Media.Media3D. ModelUIElement3D object, providing a System.Windows.Media.Media3D.GeometryModel3D object as its content. The GeometryModel3D will contain the data representing the model through its Geometry property, storing a System.Windows.Media.Media3D.MeshGeometry3D.

How It Works

In 3D graphics, shapes are generally broken down into triangles; a triangle is the simplest closed shape that can be rendered in three dimensions. It might come as a surprise to find that a line is not the simplest shape, but a line is a one-dimensional object, having only a length. In 3D, a line is a series of squares or cubes, each of which is made up of several triangles. There are myriad reasons as to why a triangle was selected to be the fundamental object in 3D graphics, but it's a discussion that is beyond the scope of this recipe. For more information, see http://en.wikipedia.org/wiki/Polygon_(computer_graphics).

Knowing this, it will come as no surprise to learn that the data for defining a mesh (remember, a collection of tessellated triangles) is based around triangles. The way in which the data for a mesh is defined may seem odd at first but will soon seem more logical as you become accustomed to the 3D world. The first stage in defining a mesh is to provide a System.Windows.Media. Media3D.Point3DCollection object for the mesh's Positions property, detailing the points for each vertex of each triangle in the mesh. In XAML, the values can be defined in a list, with or without separating commas. It is important to ensure the number of 3D points defined (X, Y, and Z coordinates) is a multiple of 3. The order in which the points are defined is not important, as long as each individual point is kept together.

The second stage is to define a System.Windows.Media.Int32Collection object for the mesh's TriangleIndicies property, detailing the order in which the points defined in the mesh's Positions property are to be used. Again, these values can be defined as a string of space-separated values when defined in XAML. The order of the values in this case is important and is used when determining the surface normal for the triangle. When the points of a triangle are specified in counterclockwise order, the triangle is rendered facing toward the camera, whereas triangles that are defined in clockwise order are rendered such that they are facing away from the camera.

The two preceding stages will give you a model that is ready for rendering. Once the model is given a material (see recipe 10-5), it will be visible in your 3D viewport. An additional stage of configuration is to define a list of vertex normals for the triangles you have defined. A vertex normal is given for each vertex (a point at the corner of the model where several triangles meet and is used when lighting a model. A vertex normal is defined as a normalized vector and is calculated as being the average of the surface normals of each triangle that shares a given vertex. The surface normal of a triangle is a vector that is perpendicular (at a right angle to) the face of the triangle.

If no value is defined for the Normals property of a MeshGeometry3D object, WPF will determine the values for you, based on the winding order of your triangles. Should you want to override the inferred values or specify your own vertex normals to achieve a desired effect or smooth out an artifact, you can provide a System.Windows.Media.Media3D.Vector3DCollection containing the vector that describes each vertex normal. The order that vertex normals are supplied should match the order in which the positions of each vertex are given, in the Positions property.

The Code

The following XAML demonstrates rendering some simple models within a Viewport3D control. Four triangles are created and displayed in the Viewport3D, with each triangle having the same color and a different rotation as the others.

<Window Background="Black"
  x:Class="Reipce_10_03.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1" Height="300" Width="300" Loaded="Window11_Loaded">
  <Window.Resources>
    <MeshGeometry3D
      x:Key="triangleMesh"
      Positions="-1,-1,0 1,-2,-1 1,1,0"
      TriangleIndices="0 1 2" />
  </Window.Resources>
  <Viewport3D x:Name="vp">
    <Viewport3D.Camera>
      <PerspectiveCamera
        LookDirection="0,0,-1"
        Position="0,0,5" />
    </Viewport3D.Camera>
    <ModelVisual3D>
      <ModelVisual3D.Content>
        <PointLight Position="0,-1,1" Color="White" />
      </ModelVisual3D.Content>
    </ModelVisual3D>
  </Viewport3D>
</Window>

The following code defines the content for the previous markup's code-behind file. The code defines a handler for the System.Windows.Window.Loaded event, which is added in the previous markup. When the method is invoked, it creates four triangles and rotates them before adding them to the Viewport3D. Figure 10-3 demonstrates the scene.

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Reipce_10_03
{
   public partial class Window1 : Window
   {
       public Window1()
       {
           InitializeComponent();
       }


       private void Window11_Loaded(object sender, RoutedEventArgs e)
       {
           //Get a reference to the triangleMesh defined in markup
           MeshGeometry3D triangleMesh
               = (MeshGeometry3D)TryFindResource("triangleMesh");
           //Create four triangles
           for (int i = 0; i < 4; i++)
           {
                //Create a new model and geometry object
                ModelVisual3D modelVisual3D = new ModelVisual3D();
                GeometryModel3D geometryModel3D
                    = new GeometryModel3D();
                //Set the GeometryModel3D's Geometry to the triangleMesh
                geometryModel3D.Geometry = triangleMesh;
                //Give the model a material
                geometryModel3D.Material
                    = new DiffuseMaterial(Brushes.Firebrick);
                //Set the content of the ModelVisual3D
                modelVisual3D.Content = geometryModel3D;
                //We want to rotate each triangle so that they overlap
                //and intersect
                RotateTransform3D rotateTransform
                    = new RotateTransform3D();
                rotateTransform.Rotation
                    = new AxisAngleRotation3D(new Vector3D(0, 0, −1),
                                              i * 90);

                //Apply the transformation
                modelVisual3D.Transform = rotateTransform;
                //Add the new model to the Viewport3D's children
                vp.Children.Add(modelVisual3D);
            }
        }
    }
}
Demonstration of a simple 3D scene containing four triangles that overlap and intersect. The single point light at the bottom of the scene provides illumination.

Figure 10-3. Demonstration of a simple 3D scene containing four triangles that overlap and intersect. The single point light at the bottom of the scene provides illumination.

Light a Scene

Problem

You need to be able to set the lighting within a scene, either using a natural ambient light or replicating other types of light sources.

Solution

Use an implementation of the abstract System.Windows.Media.Media3D.Light class to add point or directional lighting to your System.Windows.Controls.Viewport3D. WPF provides support for the following light source types:

  • Ambient light (System.Windows.Media.Media3D.AmbientLight)

  • Directional light (System.Windows.Media.Media3D.DirectionalLight)

  • Point light (System.Windows.Media.Media3D.PointLight)

  • Spotlight (System.Windows.Media.Media3D.SpotLight)

How It Works

The way a 3D scene is lit can have a huge impact on how realistic it appears to the user. All 3D scenes require some level of lighting; otherwise, you wouldn't be able to see anything. It would be in the dark. The actual type and number of lights required will depend on what you are trying to achieve. If you wanted to create a simple carousel control, you would not need anything more than some bright ambient lighting, whereas if you were creating a program for real-estate agents to provide virtual walk-throughs, lighting would be very important.

Each type of light has a Color dependency property, enabling you to set the color of your light source to any System.Windows.Media.Color value. It may be useful to note that because lights are actually models themselves, you are able to transform and animate them in the same way you would transform or animate other models.

Of the different types of lighting, ambient light is the simplest and can be thought of as daylight, a uniform level of light that is present in all parts of a scene. It doesn't cast any shadows but provides the most basic form of illumination for your 3D objects.

Directional lighting is a step on from ambient lighting and adds a direction, as a System. Windows.Media.Media3D.Vector3D, into the mix. Directional light travels in the given direction with uniform coverage.

The final two types of light, PointLight and SpotLight, derive from System.Windows. Media.Media3D.PointLightBase, which itself inherits from Light. A PointLight can be thought of as a positional light, a light source from a point in space. PointLight objects have a Position dependency property, of type System.Windows.Media.Media3D.Point3D, defining the location of the light in space, from which light of the specified color is emitted uniformly in all directions.

The PointLight and SpotLight objects also support attenuation factors, a value that indicates the distance at which the brightness (or luminosity) of the light begins to fade. This is handy if you want to model low-power light sources that don't have an infinite range such as candles or lightbulbs.

Note

You can also create a light source by giving a model a System.Windows.Media.Media3D.EmissiveMaterial. An emissive material is effectively a light source where you can specify the size and shape, and it is taken into account during any lighting calculations.

The Code

The following XAML uses a series of Viewport3D controls, each with a single polygon and different type of lighting, to demonstrate the effect that lighting has on your 3D scenes (see Figure 10-4).

<Window
  x:Class="Recipe_10_04.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


  Title="Recipe_10_04" Height="300" Width="300" Loaded="Window1_Loaded">
  <Window.Resources>
<MeshGeometry3D
      x:Key="triangleMesh"
      Positions="-1,-1,0 1,-1,-2 1,1,0"
      TriangleIndices="0 1 2" />
  </Window.Resources>
  <UniformGrid>
    <!-- Ambient light -->
    <Viewport3D x:Name="vp1">
      <Viewport3D.Camera>
        <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
      </Viewport3D.Camera>
      <ModelVisual3D>
        <ModelVisual3D.Content>
          <AmbientLight Color="White" />
        </ModelVisual3D.Content>
      </ModelVisual3D>
    </Viewport3D>
    <!-- Point light -->
    <Viewport3D x:Name="vp2">
      <Viewport3D.Camera>
        <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
      </Viewport3D.Camera>
      <ModelVisual3D>
        <ModelVisual3D.Content>
          <PointLight Position="0,-1,1" Color="White" />
        </ModelVisual3D.Content>
      </ModelVisual3D>
    </Viewport3D>
    <!-- Directional light -->
    <Viewport3D x:Name="vp3">
      <Viewport3D.Camera>
        <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
      </Viewport3D.Camera>
      <ModelVisual3D>
        <ModelVisual3D.Content>
          <DirectionalLight Direction="-1,-1,-1" Color="White" />
        </ModelVisual3D.Content>
      </ModelVisual3D>
    </Viewport3D>
    <!-- Spotlight -->
    <Viewport3D x:Name="vp4">
      <Viewport3D.Camera>
        <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
      </Viewport3D.Camera>
      <ModelVisual3D>
        <ModelVisual3D.Content>
          <SpotLight
Range="10"
            Direction="0,0,-1"
            OuterConeAngle="25"
            InnerConeAngle="20"
            Position="0,0,9"
            LinearAttenuation="0.1"
            Color="White" />
        </ModelVisual3D.Content>
      </ModelVisual3D>
    </Viewport3D>
  </UniformGrid>
</Window>

The following code defines the content of the Window1.xaml.cs file. This code contains the handler for the System.Windows.Window.Loaded event that was added in markup. The handler creates four triangles in each of the Viewport3D controls defined in markup. Although the same effect could have been achieved in markup, performing the triangle generation in code keeps the markup less cluttered, drawing focus to the lighting objects.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;


namespace Recipe_10_04
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }


        //Handler for the Window1.Loaded event
        private void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            //Get a reference to the triangleMesh defined in markup
            MeshGeometry3D triangleMesh
                = (MeshGeometry3D)TryFindResource("triangleMesh");
            //Create our pattern of triangles for each Viewport3D
            CreateTriangles(vp1, 4, triangleMesh);
            CreateTriangles(vp2, 4, triangleMesh);
            CreateTriangles(vp3, 4, triangleMesh);
            CreateTriangles(vp4, 4, triangleMesh);
        }


        private void CreateTriangles(Viewport3D viewport3D,
            int triangleCount, MeshGeometry3D triangleMesh)
{
       //Create four triangles
       for (int i = 0; i < 4; i++)
       {
            //Create a new model and geometry object
            ModelVisual3D modelVisual3D = new ModelVisual3D();
            GeometryModel3D geometryModel3D
                = new GeometryModel3D();
            //Set the GeometryModel3D's Geometry to the triangleMesh
            geometryModel3D.Geometry = triangleMesh;
            //Give the model a material
            geometryModel3D.Material
                = new DiffuseMaterial(Brushes.Firebrick);
            //Set the content of the ModelVisual3D
            modelVisual3D.Content = geometryModel3D;
            //We want to rotate each triangle so that they overlap
            //and intersect
            RotateTransform3D rotateTransform
                 = new RotateTransform3D();
            rotateTransform.Rotation
                 = new AxisAngleRotation3D(new Vector3D(0, 0, −1),
                                           i * 90);
            //Apply the transformation
            modelVisual3D.Transform = rotateTransform;
            //Add the new model to the Viewport3D's children
            viewport3D.Children.Add(modelVisual3D);
          }
       }
   }
}
Demonstrates the different types of lighting when used to light the same collection of models

Figure 10-4. Demonstrates the different types of lighting when used to light the same collection of models

Specify a Material for a Model

Problem

You need to be able to specify the type and characteristics of the material applied to a System.Windows.Media.Media3D.GemoetryModel3D or System.Windows.Media.Media3D. Viewport2DVisual3D.

Solution

Use an implementation of the abstract class System.Windows.Media.Media3D.Material to specify the type of material to be used and characteristics such as color.

How It Works

The type of material used when creating a 3D object will affect the way light interacts with the object, as well as the final color of the object and any light that is reflected. WPF provides support for three categories of material: diffuse, specular, and emissive. Each material type has a Brush dependency property that is used to specify the color/visual used to paint the material.

The most basic and commonly used material is the diffuse material, implemented with the System.Windows.Media.Media3D.DiffuseMaterial class. A diffuse material is one that has a very uneven surface, causing reflected light rays that strike its surface to be scattered in all directions. This scattered light uniformly spreads out over a hemisphere around the point of incident and will appear the same, regardless of the camera's position. When lighting a diffuse material, gradients are often seen where the intensity of the reflected light drops off as you move out from the point of incident, giving some very pleasing and realistic effects. Diffuse materials are used when modeling a matte surface.

A specular material is quite different from a diffuse material and is used when modeling hard, glossy objects like some plastics or metals, because specular material will show highlights where light is reflected. The amount by which a highlight is spread over the surface of the material surrounding a point of incident is configured using the SpecularPower property of a System.Windows.Media.Media3D.SpecularMaterial. A lower value will result in a larger spread, and a higher value will give smaller, more concentrated highlights. Specular materials also differ from diffuse materials in the way their color contributes to the overall value. Generally, these values are averaged and combined, but for a specular material, the values are additive and will add to the value of light at that point. If there is a great deal of light being reflected, the value may exceed 100 percent, in which case the material will be colored white in this area. For this reason, a specular material is almost always defined within a System.Windows.Media. Media3D.MaterialGroup, over the top of some DiffuseMaterial, adding any highlights that may be present.

The third type of material is an emissive material. These materials are different again from the other two materials in that objects with an emissive material will emit light evenly across its surface. Despite this, an object with an emissive material is not classed as a light source, and its contribution to the final color of a light ray is calculated differently. Like SpecularMaterial objects, an EmissiveMaterial is almost always used in a MaterialGroup.

You may also notice that objects that have a Material property also have a BackMaterial property. During the rendering process of a 3D scene, any polygons that are facing away from the camera, that is, when the angle between the polygon's surface normal and the view direction is greater than 90 degrees, are removed because they are not visible. This process is known as back-face culling; in other words, polygons facing their backs toward the camera are culled.

This is not a problem when you have a closed, solid 3D shape, but it can be problematic when dealing with lamina objects, composed only of a single layer of polygons, such as a flag. In this instance, you may want to see the BackMaterial property to some material that will be displayed when the model is facing away from the camera. The back material could be as simple as a mirror image of the front material or something different altogether.

The Code

The following XAML demonstrates how to use the different types of materials outlined earlier. The example contains three Viewport3D controls, each with a single polygon, rendered with one of the material types listed earlier. This example illustrates how the material of a 3D object can affect the way it appears when rendered (see Figure 10-5).

<Window x:Class="Recipe_10_05.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Recipe_10_05" Height="300" Width="800" Loaded="Window1_Loaded">
  <Window.Resources>
    <MeshGeometry3D
      x:Key="triangleMesh"
      Positions="-1,-1,0 1,-1,-2 1,1,0"
      TriangleIndices="0 1 2" />


    <DiffuseMaterial x:Key="diffuseMaterial" Brush="Firebrick" />


      <MaterialGroup x:Key="specularMaterial">
        <StaticResource ResourceKey="diffuseMaterial" />
        <SpecularMaterial
          Brush="White"
          SpecularPower="5" />
      </MaterialGroup>


     <MaterialGroup x:Key="emissiveMaterial">
       <StaticResource ResourceKey="diffuseMaterial" />
       <EmissiveMaterial Color="Yellow" />
     </MaterialGroup>


   </Window.Resources>
   <Grid>
     <Grid.ColumnDefinitions>
     <ColumnDefinition />
     <ColumnDefinition />
     <ColumnDefinition />
   </Grid.ColumnDefinitions>
<Grid.RowDefinitions>
     <RowDefinition />
     <RowDefinition Height="20" />
   </Grid.RowDefinitions>


   <!-- Diffuse Material -->
   <Viewport3D x:Name="vp1">
     <Viewport3D.Camera>
       <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
     </Viewport3D.Camera>
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <PointLight Position="0,-1,2" Color="White" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
   </Viewport3D>
   <!-- Specular Material -->
   <Viewport3D x:Name="vp2" Grid.Column="1">
     <Viewport3D.Camera>
       <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
     </Viewport3D.Camera>
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <PointLight Position="0,-1,2" Color="White" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
   </Viewport3D>
   <!-- Emissive Material -->
   <Viewport3D x:Name="vp3" Grid.Column="2">
     <Viewport3D.Camera>
       <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
     </Viewport3D.Camera>
     <ModelVisual3D>
       <ModelVisual3D.Content>
         <PointLight Position="0,-1,2" Color="White" />
       </ModelVisual3D.Content>
     </ModelVisual3D>
   </Viewport3D>


   <!-- Labels -->
   <TextBlock
     Text="Diffuse Material"
     Grid.Row="1"
     HorizontalAlignment="Center" />
   <TextBlock
     Text="Specular Material"
     Grid.Row="1"
Grid.Column="1"
      HorizontalAlignment="Center" />
    <TextBlock
      Text="Emissive Material"
      Grid.Row="1"
      Grid.Column="2"
      HorizontalAlignment="Center" />
  </Grid>
</Window>

The following code defines the content of the Window1.xaml.cs file. This code contains the handler for the System.Windows.Window.Loaded event that was added in markup. The handler creates four triangles in each of the Viewport3D controls defined in markup. Although the same effect could have been achieved in markup, performing the triangle generation in code keeps the markup less cluttered, drawing focus to the lighting objects.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Media3D;


namespace Recipe_10_05
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }


        //Handler for the Window1.Loaded event
        private void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            //Get a reference to the triangleMesh defined in markup
            MeshGeometry3D triangleMesh
                = (MeshGeometry3D)TryFindResource("triangleMesh");
            //Create our pattern of triangles for each Viewport3D
            CreateTriangles(vp1, 4, triangleMesh,
                (Material)TryFindResource("diffuseMaterial"));
            CreateTriangles(vp2, 4, triangleMesh,
                (Material)TryFindResource("specularMaterial"));
            CreateTriangles(vp3, 4, triangleMesh,
                (Material)TryFindResource("emissiveMaterial"));
         }
private void CreateTriangles(Viewport3D viewport3D,
       int triangleCount, MeshGeometry3D triangleMesh,
       Material material)
     {
       //Create four triangles
       for (int i = 0; i < 4; i++)
       {
            //Create a new model and geometry object
            ModelVisual3D modelVisual3D = new ModelVisual3D();
            GeometryModel3D geometryModel3D
                = new GeometryModel3D();
            //Set the GeometryModel3D's Geometry to the triangleMesh
            geometryModel3D.Geometry = triangleMesh;
            //Give the model a material
            geometryModel3D.Material = material;
            //Set the content of the ModelVisual3D
            modelVisual3D.Content = geometryModel3D;
            //We want to rotate each triangle so that they overlap
            //and intersect
            RotateTransform3D rotateTransform
                = new RotateTransform3D();
            rotateTransform.Rotation
                = new AxisAngleRotation3D(new Vector3D(0, 0, −1),
                                          i * 90);
            //Apply the transformation
            modelVisual3D.Transform = rotateTransform;
            //Add the new model to the Viewport3D's children
            viewport3D.Children.Add(modelVisual3D);
         }
      }
   }
}
Examples of the different types of materials and their effects on a model's lighting

Figure 10-5. Examples of the different types of materials and their effects on a model's lighting

Apply Textures to a Model

Problem

You have a 3D model that you want to apply a texture to, giving it a rich and possibly realistic appearance.

Solution

When defining a 3D model, supply the TextureCoordinates property of a System.Windows. Media.Media3D.MeshGeometry3D with a System.Windows.Media.PointCollection detailing the texture coordinates when mapping a texture on to the object. Then supply the desired texture as a System.Windows.Media.Brush, that is, a System.Windows.Media.ImageBrush.

How It Works

Texture mapping is an age-old technique in computer graphics and is the process of applying some image or texture to a rendered object. This allows you to wrap your 3D objects in lush images, increasing the richness of the application and providing a realistic image to the viewer. Performing texture mapping is often a perilous task and involves you mapping values between coordinate systems, thereby transforming the points to fit the profile of the object they are being mapped to. Luckily, WPF does a huge amount of work for you, leaving little more than for you to specify the texture coordinates for each vertex in your model and what you want to use to paint the object.

When defining a MeshGeometry3D object, you have the option of supplying texture coordinates as a PointCollection. The idea is that for each vertex in the model, you specify the coordinate it maps to on the source texture. This is done by listing the 2D texture coordinates in the same order as the vertices were defined; for example, the first texture coordinate you specify in your PointCollection will be used when texture mapping the first triangle in the model.

The texture coordinates are specified as a value between 0 and 1, inclusive, where x = 0 maps to the left of the texture image and x = 1 maps to the right of the texture image. Similarly for y, 0 maps to the top of the source image, and 1 maps to the bottom. Think of them as a ratio, describing how far across or down the source image a point should map to.

So, now that you know how to specify your texture coordinates, you need to specify the texture! In true WPF style, this process is fairly painless and is carried out using a System. Windows.Media.Media3D.Material object. Because the Brush property of a Material object is a System.Windows.Media.Brush, objects such as a System.Windows.Media.ImageBrush and System. Windows.Media.VisualBrush can be used. To use an image file from disk as a texture, you would create a System.Windows.Media.Media3D.DiffuseMaterial and specify an ImageBrush as the value for its Brush property, setting the ImageSource property of the ImageBrush to the path of the image you want to display.

Should you use a transparent image or control as the brush for your model, you may want to place the texture in a System.Windows.Media.Media3D.MaterialGroup and place a soft-colored material underneath the texture.

The Code

The following XAML demonstrates how to use Ellipse, Rectangle, or Polygon elements to draw simple shapes in a System.Windows.Controls.UniformGrid (see Figure 10-6).

<Window
  x:Class="Recipe_10_06.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="Thistle" Height="400" Width="400" Title="Recipe_10_06">
  <Window.Resources>
    <!-- Front, left square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontLeft"
      Positions="-1,-1,1 1,-1,1 1,1,1 −1,1,1"
      TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />
    <!-- Front, right square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontRight"
      Positions="1,-1,1 1,-1,-1 1,1,-1 1,1,1"
      TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />
    <!-- Top square -->
    <MeshGeometry3D
      x:Key="squareMeshTop"
      Positions="-1,1,1 1,1,1 1,1,-1 −1,1,-1"
      TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />


    <DiffuseMaterial x:Key="textureFrontLeft">
      <DiffuseMaterial.Brush>
        <ImageBrush ImageSource="weesam.jpg" />
      </DiffuseMaterial.Brush>
    </DiffuseMaterial>


    <DiffuseMaterial x:Key="textureFrontRight">
      <DiffuseMaterial.Brush>
        <ImageBrush ImageSource="weejayne.jpg" />
      </DiffuseMaterial.Brush>
    </DiffuseMaterial>


    <MaterialGroup x:Key="textureTop">
      <DiffuseMaterial Brush="Olive" />
      <DiffuseMaterial>
        <DiffuseMaterial.Brush>
          <VisualBrush Stretch="Uniform">
            <VisualBrush.Visual>
<Border
                Margin="50,0"
                BorderThickness="1"
                CornerRadius="5"
                BorderBrush="Firebrick">
                <Border.RenderTransform>
                  <RotateTransform Angle="-45" />
                </Border.RenderTransform>
                <TextBlock Text="I am a VisualBrush!" />
              </Border>
            </VisualBrush.Visual>
          </VisualBrush>
        </DiffuseMaterial.Brush>
      </DiffuseMaterial>
    </MaterialGroup>
 </Window.Resources>


 <Viewport3D>
   <Viewport3D.Camera>
     <PerspectiveCamera Position="4,3.5,4" LookDirection="-1,-0.7,-1" />
  </Viewport3D.Camera>
  <!--Front left side-->
  <ModelVisual3D>
     <ModelVisual3D.Content>
     <GeometryModel3D
       Geometry="{StaticResource squareMeshFrontLeft}"
       Material="{StaticResource textureFrontLeft}" />
       </ModelVisual3D.Content>
  </ModelVisual3D>
  <!-- Front right side -->
  <ModelVisual3D>
    <ModelVisual3D.Content>
    <GeometryModel3D
         Geometry="{StaticResource squareMeshFrontRight}"
         Material="{StaticResource textureFrontRight}" />
         </ModelVisual3D.Content>
   </ModelVisual3D>
   <!-- Top side -->
   <ModelVisual3D>
     <ModelVisual3D.Content>
     <GeometryModel3D
       Geometry="{StaticResource squareMeshTop}"
       Material="{StaticResource textureTop}" />
       </ModelVisual3D.Content>
   </ModelVisual3D>


   <ModelVisual3D>
     <ModelVisual3D.Content>
<AmbientLight Color="White" />
    </ModelVisual3D.Content>
  </ModelVisual3D>
 </Viewport3D>
</Window>
Examples of texture mapping using both images loaded from disk as well as a visual brush

Figure 10-6. Examples of texture mapping using both images loaded from disk as well as a visual brush

Interact with 3D Objects

Problem

You need to detect when your 3D objects are clicked with the mouse or when the mouse is placed over the object. This includes clicking objects that overlap but at different distances from the camera.

Solution

Build your 3D models as System.Windows.Media.Media3D.ModelUIElement3D objects in a System.Windows.Controls.Viewport3D. The ModelUIElement3D object provides support for input, focus, and the associated events.

How It Works

The ModelUIElement3D object is very similar to the ModelVisual3D object, with both classes descending from System.Windows.Media.Media3D.Visual3D, although ModelUIElement provides the added richness that is user input and focus handling. This extra functionality isn't quite free, though, because it will add overhead to your 3D scene. If performance is key to your application, you may want to implement your own user input handling, implementing only the functionality you require.

Harnessing the extra functionality is as simple as adding event handlers to the required events and executing your custom code. This enables you to add things like tool tips or apply animations to your models, something not possible in XAML because ModelVisual3D objects and its descendents do not support triggers.

When handling user input in a scene with more than one model, the distance that an object is from the camera will be taken into consideration when determining which object was clicked. This means that if you have two objects that overlap each other but are positioned at different depths, the object closest to the camera, and only that object, will receive the event.

Note

If two or more objects overlap and the mouse click event is at a point where both objects are at the same depth, you will encounter z-fighting! This is where two pixels at the same depth may be selected at random in a nondeterministic fashion and will be particularly noticeable in animation.

The Code

The following XAML demonstrates how to use handling user input events on layered objects in a 3D scene. A single Viewport3D control contains three polygons as ModelUIElement3D objects, with handlers on each of the polygon's MouseDown events. Observe how it doesn't matter where on the foremost triangle you click; the polygon1_MouseDown method is invoked as polygon1 is closer to the camera than the other two polygons in the scene (see Figure 10-7).

<Window
  x:Class="Recipe_10_07.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Recipe_10_07" Height="300" Width="300">
  <Viewport3D>
    <Viewport3D.Camera>
      <PerspectiveCamera LookDirection="0,0,-1" Position="0,0,5" />
    </Viewport3D.Camera>
    <ModelVisual3D>
      <ModelVisual3D.Content>
        <AmbientLight Color="White" />
      </ModelVisual3D.Content>
    </ModelVisual3D>
    <!-- Polygon 1 -->
    <ModelUIElement3D MouseDown="polygon1_MouseDown">
      <GeometryModel3D>
        <GeometryModel3D.Geometry>
          <MeshGeometry3D
            Positions="-1,-1,1 1,-1,1 1,1,1"
TriangleIndices="0 1 2" />
        </GeometryModel3D.Geometry>
        <GeometryModel3D.Material>
          <DiffuseMaterial Brush="Firebrick" />
        </GeometryModel3D.Material>
      </GeometryModel3D>
    </ModelUIElement3D>
    <!-- Polygon 2 -->
    <ModelUIElement3D MouseDown="polygon2_MouseDown">
      <GeometryModel3D>
        <GeometryModel3D.Geometry>
          <MeshGeometry3D
            Positions="1,-1,0 1,1,0 −1,1,0"
            TriangleIndices="0 1 2" />
        </GeometryModel3D.Geometry>
      <GeometryModel3D.Material>
          <DiffuseMaterial Brush="CornflowerBlue" />
        </GeometryModel3D.Material>
      </GeometryModel3D>
    </ModelUIElement3D>
    <!-- Polygon 3 -->
    <ModelUIElement3D MouseDown="polygon3_MouseDown">
      <GeometryModel3D>
        <GeometryModel3D.Geometry>
          <MeshGeometry3D
            Positions="1,0,0 1,1,0 0,1,0"
            TriangleIndices="0 1 2" />
          </GeometryModel3D.Geometry>
        <GeometryModel3D.Material>
            <DiffuseMaterial Brush="OrangeRed"/>
          </GeometryModel3D.Material>
       </GeometryModel3D>
    </ModelUIElement3D>
  </Viewport3D>
</Window>

The following code defines the content of the code-behind for the previous markup. The code defines the three event handlers that are added in the markup.

using System.Windows;
using System.Windows.Input;


namespace Recipe_10_07
{
     public partial class Window1 : Window
     {
        public Window1()
{
            InitializeComponent();
         }


         private void polygon1_MouseDown(object sender,
            MouseButtonEventArgs e)
         {
            MessageBox.Show("polygon1_MouseDown", "Recipe_10_07");
         }


         private void polygon2_MouseDown(object sender,
            MouseButtonEventArgs e)
         {
            MessageBox.Show("polygon2_MouseDown", "Recipe_10_07");
         }


         private void polygon3_MouseDown(object sender,
            MouseButtonEventArgs e)
         {
            MessageBox.Show("polygon3_MouseDown", "Recipe_10_07");
         }
     }
}
Example of user input on objects that overlap but are at different depths with respect to the camera

Figure 10-7. Example of user input on objects that overlap but are at different depths with respect to the camera

Use a 2D Control in a 3D Scene

Problem

You need to use some of the standard 2D controls such as System.Windows.Controls.Button or System.Windows.Controls.TextBox in a 3D scene, allowing the control to be fully interactive.

Solution

Use a System.Windows.Media.Media3D.Viewport2DVisual3D to host the required 2D control.

How It Works

The Viewport2DVisual3D control is used to host 2D content in a 3D content control, complementing the System.Windows.Media.Media3D.Viewport3DVisual control that hosts 3D content within a 2D visual. This is a very powerful feature that enables you to easily build a powerful and rich 3D user interface, retaining the use of 2D controls such as System.Windows.Controls.Button objects and System.Windows.Controls.TextBox objects.

When using 2D content in a 3D visual, WPF can carry out coordinate system transformations, mapping the position of any input events such as mouse clicks in to their 2D equivalents. This is great if you are displaying a custom control with multiple interactive regions or child controls because you are able to process user interaction in the same way you would normally in 2D.

Note

The System.Windows.Media.Media3D.Viewport2DVisual3D class was introduced in .NET 3.5.

The Code

The following XAML demonstrates how to use 2D content in a 3D model by rendering various standard controls on to the faces of three squares, which are joined together to form the visible half of a cube (see Figure 10-8). Notice how all the controls respond to user input such as hover states, click states, and so on. When the button is clicked, a message is displayed that shows the location at which the mouse was pressed, relative to the button's own coordinate system, implicitly projecting and transforming the 3D point into 2D.

<Window
  x:Class="Recipe_10_08.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Recipe_10_08" Height="300" Width="300">
  <Window.Resources>
    <!-- Front, left square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontLeft"
      Positions="-1,-1,1 1,-1,1 1,1,1 −1,1,1"
TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />
    <!-- Front, right square -->
    <MeshGeometry3D
      x:Key="squareMeshFrontRight"
      Positions="1,-1,1 1,-1,-1 1,1,-1 1,1,1"
      TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />
    <!-- Top square -->
    <MeshGeometry3D
      x:Key="squareMeshTop"
      Positions="-1,1,1 1,1,1 1,1,-1 −1,1,-1"
      TriangleIndices="0 1 2 0 2 3"
      TextureCoordinates="0,1 1,1 1,0 0,0" />


    <DiffuseMaterial
      x:Key="visualHostMaterial"
      Brush="White"
      Viewport2DVisual3D.IsVisualHostMaterial="True" />
  </Window.Resources>
  <Viewport3D>
    <Viewport3D.Camera>
      <PerspectiveCamera Position="4,2.5,4" LookDirection="-1,-0.7,-1" />
    </Viewport3D.Camera>
    <Viewport2DVisual3D
      Material="{StaticResource visualHostMaterial}"
      Geometry="{StaticResource squareMeshFrontLeft}">
      <StackPanel>
        <Slider />
        <Button Click="Button_ClickMe_Click" >
          <DockPanel>
            <Ellipse
              Width="20"
              Height="20"
              Stroke="Black"
              Fill="Purple"
              DockPanel.Dock="Right" />
            <TextBlock VerticalAlignment="Center" Text="Click me!" />
          </DockPanel>
        </Button>
      </StackPanel>
    </Viewport2DVisual3D>


    <Viewport2DVisual3D
      Material="{StaticResource visualHostMaterial}"
      Geometry="{StaticResource squareMeshFrontRight}">
      <TextBox
Text="This is a TextBox!"
        AcceptsReturn="True"
        Width="200"
        Height="200" />
    </Viewport2DVisual3D>


    <Viewport2DVisual3D
      Material="{StaticResource visualHostMaterial}"
      Geometry="{StaticResource squareMeshTop}">
      <StackPanel>
        <RadioButton GroupName="rgTest" IsChecked="True" Content="RadioButton 1" />
        <RadioButton GroupName="rgTest" Content="RadioButton 2" />
        <RadioButton GroupName="rgTest" Content="RadioButton 3" />
        <CheckBox IsChecked="True" Content="CheckBox 1" />
        <CheckBox IsChecked="True" Content="CheckBox 2" />
        <CheckBox IsChecked="True" Content="CheckBox 3" />
        <ComboBox>
          <ComboBox.Items>
            <ComboBoxItem Content="Item 1" />
            <ComboBoxItem Content="Item 2" />
            <ComboBoxItem Content="Item 3" />
          </ComboBox.Items>
        </ComboBox>
      </StackPanel>
   </Viewport2DVisual3D>


   <ModelVisual3D>
      <ModelVisual3D.Content>
        <AmbientLight Color="White" />
      </ModelVisual3D.Content>
   </ModelVisual3D>
   </Viewport3D>
</Window>

The following code defines the content of the Window1.xaml.cs file:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;


namespace Recipe_10_08
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
{
        public Window1()
        {
           InitializeComponent();
        }


        private void Button_ClickMe_Click(object sender,
            RoutedEventArgs e)
        {
            //Get the position of the mouse, relative to the
            //button that was clicked.
            Point? position = Mouse.GetPosition(sender as Button);
            //Build a message string to display to the user.
            string msg = string.Format("Wow, you just clicked a " +
                "2D button in 3D!{0}{0}You clicked the button at" +
                " x = {1}, y = {2}", Environment.NewLine,
                (int)position.Value.X, (int)position.Value.Y);


            MessageBox.Show(msg, "Recipe_10_08");
         }
     }
}
Demonstrating the use of 2D content in a 3D model. Clicking the button also retrieves the point at which the button was clicked, relative to the button's model coordinate system.

Figure 10-8. Demonstrating the use of 2D content in a 3D model. Clicking the button also retrieves the point at which the button was clicked, relative to the button's model coordinate system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset