technodesigner

Digital design

Olivier Dewit
Perspective : Easy 3D programming with WPF

Perspective : Easy 3D programming with WPF

A few years ago, I was impressed by the ease of 3D programming with VPython . This library made it possible to build in language Python 3D models starting from basic (cube, cylinder, sphere, etc.) or composite (arrow) geometrical objects, and to anime them. The default visualization window allowed to move the camera around the scene, and to zoom. Thus it was easy to get with a few lines of code a very spectacular application. VPython was very much used to illustrate physical models. Example .

WPF uses Direct3D technology to provide creation services for 2D user interface but also for 3D scenes. You can find many references about this subject like this tutorial , who describes the different steps of the creation of a cube. Even if WPF is much easier to program than Direct3D, we are still far from the VPython productivity. WPF misses high-level classes for the 3D programming (cube, cylinder, sphere, etc.). Several authors make this constatation and give some solutions (of which however none goes as far as VPython) :

Since 2006, I developed the Perspective class library, which offers WPF functionalities and productivity close to those of VPython while integrating technological innovations such as interactive 3D controls.

The continuation of the article applies to the version 2.0 of Perspective, which requires .NET 4.0 and Visual Studio 2010.

Wpf3D Basics

Getting and installing Perspective

3D WPF programming basics are not explained here, but the text contains some links towards the SDK documentation .

To use the Perspective classes in a Xaml file, you have to define 2 XML namespaces referring the Perspective assemblies :

<Page x:Class="PerspectiveDemo.UI.Pages.pWpf3D.Box3D"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:p="http://www.codeplex.com/perspective"
...

Basically, a 3D scene is displayed in a Viewport3D object. Then we have to configure the camera and lights . In order to be able to move the point of view and to turn around the model, it is possible to apply rotations to the camera (RotateTransform3D) around the X, Y and Z axis . A scaling (ScaleTransform3D) may be applied to simulate a zoom effect. These transformations may be bound to sliders.

In practice, it is tedious and repetitive. Perspective is consistent with this approach, but provides, since version 0.9, the Workshop3D element which includes a Viewport3D, a camera, lighting and a system of zoom and displacement of view.

<Page ...>
    <p:Workshop3D>
      ...
    </p:Workshop3D>
</Page>

We can now instanciate our first visual Wpf3D object : the coordinate system (XyzAxis3D class) which will visually help us to position our 3D elements :

<p:Workshop3D>
    <p:XyzAxis3D />
</p:Workshop3D>

If we execute now the application, we can see the coordinate system.

XyzAxis3D object

The camera can be controlled by mouse (with joysticks) or by keyboard (when the Workshop3D is focused) :

Workshop3D

The main properties of XyzAxis3D are : Radius, radius of each body axe, Length, size of each axis, and Signed which dislays the negative axis.

<p:XyzAxis3D Length="5" Signed="True"/>

XyzAxis3D object

We can now begin to create our models. Let's start with a cube using the Box3D class:

<p:Workshop3D>
    <p:XyzAxis3D Length="3.0"/>
    <p:Box3D />
</p:Workshop3D>

Box3D object

Each Wpf3D object has a default material, which can be modified using the Material property.

Our cube is positioned at the origin of the coordinate system, and the edges size is of 1 unit. While reading the documentation, it may seem surprising that no property enables us to modify the position and the size of the cube. But that is completely normal. WPF uses the graphics processor (GPU) which supports instructions to transform the position or the aspect of the models. Modifying the position and the size of the cube in our code would use the main processor (CPU) instead of the GPU. To respect the WPF logic, we will use 3D transformations and assign them to the Transform property (always with the possibility of defining the transformation in the resources centralized zone) :

...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Transform3DGroup x:Key="ModelTransform">

        <TranslateTransform3D OffsetX="1.0" OffsetY="-1.0" OffsetZ="0.5" />

        <ScaleTransform3D ScaleX="1.0" ScaleY="1.0" ScaleZ="3.0" />

    </Transform3DGroup>
    ...
</ResourceDictionary>
...
<Page.Resources>
    <ResourceDictionary Source="Wpf3DResources.xaml" />
</Page.Resources>
<p:Workshop3D>
    <p:XyzAxis3D Length="5.0"/>
    <p:Box3D Transform="{StaticResource ModelTransform}"/>
</p:Workshop3D>
...

Tranformated Box3D object

This principle applies to all the basic models delivered in Perspective. Their basic dimension is the unit, and they are based on the origin of the coordinate system (the point of coordinates 0.0, 0.0, 0.0).

Interactivity

Because the Perspective 3D objects are inheriting from UIElement3D, they support keyboard and mouse interactions, and they have the corresponding events. The following example shows how to trigger a rotation of the 3D model when the mouse is over it, through the use of the events MouseEnter and MouseLeave :

<p:Workshop3D>
    <p:Box3D 
        MouseEnter="Box3D_MouseEnter"
        MouseLeave="Box3D_MouseLeave">
        <p:Box3D.Transform >
            <RotateTransform3D CenterX="0.5" CenterZ="0.5">
                <RotateTransform3D.Rotation>
                    <AxisAngleRotation3D 
                        x:Name="boxRotation"    
                        Axis="0.0, 1.0, 0.0"/>
                </RotateTransform3D.Rotation>
            </RotateTransform3D>
        </p:Box3D.Transform>
    </p:Box3D>
</p:Workshop3D>
private void Box3D_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
    // Initialization of the animation object : on 360 degrees after the current angle
    DoubleAnimation da = new DoubleAnimation(boxRotation.Angle + 360.0, new Duration(TimeSpan.FromSeconds(5.0)));
    da.RepeatBehavior = RepeatBehavior.Forever;
    // Trigger the animation on the boxRotation's Angle property
    boxRotation.ApplyAnimationClock(AxisAngleRotation3D.AngleProperty, da.CreateClock());
}
private void Box3D_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
    // current angle memorization
    double currentAngle = boxRotation.Angle;
    // Stops the animation
    boxRotation.ApplyAnimationClock(AxisAngleRotation3D.AngleProperty, null);
    // Reassignment of the angle (which otherwise resumes its initial value)
    boxRotation.Angle = currentAngle;
}

Flat models

The Square3D class defines... a 2D square !

<p:Square3D />

Square3D object

The BackMaterial property has the same role than the Material property but for the back face.

On 3D not-flat models, the BackMaterial is visible when the material is translucent (Opacity < 1.0).

Polygon-based models

Polygon3D defines a flat polygon. The SideCount property defines a regular polygon of a given side count.

<p:Polygon3D SideCount="6" />

Polygon3D object

The polygon fits in a circle of radius 1 in the X, Y plan. The polygon vertices are calculated by dividing the circumference by the SideCount value. By default, the 1st point is at 1,0,0. The InitialAngle property makes it possible to move this point according to the angle of the segment that it forms with the origin compared to the X axis. Example for an angle of 30 degrees :

<p:Polygon3D SideCount="6" InitialAngle="30.0"/>

Polygon3D object with an initial angle of 30 degrees

The RoundingRate makes it possible to round the polygon vertices. The value must be comprised between 0.0 and 0.5. It represents a side proportion submitted to the rounding for the 2 sides of a vertex. Thus a value of 0.5 generates a perfect circle. Example with a value of 0.15 :

<p:Polygon3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"/>

Rounded Polygon3D object

The bar3D class défines a bar with a polygonal section. The section has the same characteristics than a Polygon3D : side count, initial angle and rounding rate. For a better emphasizing of the rounding, we use a glossy material (using SpecularMaterial) and move the model compared to the light position.

<ResourceDictionary ...>
    <SpecularMaterial x:Key="Specular" SpecularPower="100.0" Brush="White"/>
    <MaterialGroup x:Key="GlossyMaterial">
        <DiffuseMaterial Brush="Goldenrod"/>
        <StaticResource ResourceKey="Specular"/>
    </MaterialGroup>
</ResourceDictionary>
...
<p:Bar3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Transform="{StaticResource ModelTransform}" 
    Material="{StaticResource GlossyMaterial}" />

Bar3D object

To build a cylinder with a radius of 1.0, you can grow the SideCount property :

<p:Bar3D SideCount="100" Material="{StaticResource glossyMaterial}" />

Cylinder

Conical3D is a cone with a polygonal base, and works as Bar3D .

<p:Conical3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Transform="{StaticResource ModelTransform}" 
    Material="{StaticResource GlossyMaterial}" />

Conical3D object

Finally Ring3D is a ring with a polygonal section. It has additional properties to indicate its radius and its segment count.

<p:Ring3D 
    Radius="10.0" SegmentCount="100"
    SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Material="{StaticResource GlossyMaterial}" />

Ring3D object

Spherical models

The Spherical3D class defines a sphere with a radius of 1 unit. The smoothness of the spherical aspect is defined using the ParallelCount property.

The meridian count is twice the parallel count.

<p:Spherical3D ParallelCount="100" Material="{StaticResource GlossyMaterial}"/>

Spherical3D object

It is easy to transform the sphere into an ellipsoid using a tranform object.

<p:Spherical3D ParallelCount="100" 
    Material="{StaticResource GlossyMaterial}"
    Transform="{StaticResource ModelTransform}"/>

Ellipsoid

Polyhedrons

Just for fun, the Isocahedron3D class defines a regular isocahedron , that is a 20 faces polyhedron.

<p:Isocahedron3D Material="{StaticResource GlossyMaterial}" />          

Isocahedron3D object

If we truncate its vertices, we get a truncated isocahedron... represented by the TruncatedIsocahedron3D class.

<p:Isocahedron3D Truncated="True" Material="{StaticResource GlossyMaterial}" />

Truncated Isocahedron3D object

Anyway, you see what I mean...

<p:Football3D />

Football (soccer) ballon

During the update of this article, in november 2007, I was thinking to build a rugby balloon. But no... :-(

3D pie charts

The PieSlice3D class makes it possible to build beautiful pie charts. Each PieSlice3D represents a portion, and has the properties InitialAngle and AngleValue. The IsExploded property is used to highlight a portion. By default, the pie is built around the Z axis. For a "flat" presentation, it is necessary to apply a transformation (as in the example below):

...
<Transform3DGroup x:Key="PieTransform">
    <RotateTransform3D>
        <RotateTransform3D.Rotation>
            <AxisAngleRotation3D 
            Angle="-90"
            Axis="1,0,0" />
        </RotateTransform3D.Rotation>
    </RotateTransform3D>
    <ScaleTransform3D ScaleY="0.2"/>
</Transform3DGroup>
...
<p:Workshop3D>
    <p:PieSlice3D 
        AngleValue="45" 
        IsExploded="True"
        Material="{StaticResource GlossyMaterial}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="45" AngleValue="135" 
        Material="{StaticResource GlossyRed}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="180" AngleValue="80" 
        Material="{StaticResource GlossyBlue}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="260" AngleValue="100" 
        Material="{StaticResource GlossyGreen}"
        Transform="{StaticResource PieTransform}"/>
</p:Workshop3D>

Gyroscope

Thanks to Philippe Jovelin for his contribution.

Texture

It is possible to display an image on a Square3D, by using an ImageBrush or a VisualBrush in the Material definition.

<p:Square3D >
    <p:Square3D.Material>
        <DiffuseMaterial>
            <DiffuseMaterial.Brush>
                <ImageBrush 
                    ImageSource="http://www.odewit.net/Perspective/Images/Tree.jpg" 
                    TileMode="None" 
                    Stretch="Fill"/>
            </DiffuseMaterial.Brush>
        </DiffuseMaterial>
    </p:Square3D.Material>
</p:Square3D>

Image on a Square3D

If the BackMaterial property is empty, the back face uses the default material.

You can apply a VisualBrush, an ImageBrush or a DrawingBrush on Square3D, Polygon3D, Box3D, Bar3D, Conical3D and Spherical3D.

Image on a Polygon3D
Image on a Box3D
Image on a Bar3D
Image on a Conical3D
Image on a Spherical3D

Composite models

The Wpf3D objects can be gathered in a ModelVisual3D or in a ContainerUIElement3D to form a composite model, such as this gyroscope (animated when you click on it) :

<p:Gyroscope3D />

Gyroscope The Perspective library is extensible, and it is easy to define in .NET code (C# or Visual Basic) some new composite model classes. After XyzAxis3D and Football3D, the Arrow3D class is another example :

<p:Arrow3D Material="{StaticResource GlossyMaterial}" />

Arrow

3D Controls

In its 0.4 version, Perspective has introduced the concept of 3D skinnable control .

Perspective 3D controls



Powered by the Perspective WebApp - © Olivier Dewit - Lyon, Paris, France - Legal informations