DCSIMG
Animating the Position of a 3D Camera in WPF - David Sackstein's Blog

Animating the Position of a 3D Camera in WPF

In this post we will use Point3DAnimationUsingPath from the previous post to rotate the viewpoint around a 3D cube while playing a video on all six of its faces.

You can download the complete source, with the Point3DAnimationUsingPath class from here.

Point3DAnimationUsingPath

"Look at me!
Look at me now!" said the cat.
"With a cup and a cake
On the top of my hat!
I can hold up TWO books!
I can hold up the fish!
And a litte toy ship!
And some milk on a dish!
And look!
I can hop up and down on the ball!
But that is not all!
Oh, no.
That is not all... “,  Dr.Seuss

 

Motivation

It is fairly straightforward to rotate 3D objects in WPF using the various Transform3D derived classes. But sometimes what you need is to rotate the viewer around the scene and not to rotate individual objects. That is, you need to animate the Position property of the Camera object over a path in three dimensions.

The problem is that the type of the Position property is Point3D and WPF doesnt provide a Point3DAnimationUsingPath class. I partially filled that gap with a class of that name in this previous post.

In addition, as we modify the Position of the Camera, we need to change the LookDirection so that the Camera always points to the same point in space.

To demonstrate the solution, I will place a cube at the center of the 3D coordinate system and play video on each side so you can clearly see how we are rotating around the object.

“Look at me! Look at me now!”, say I, “With a cube in the center and a video on SIX faces, I can rotate around it and look back to the center! But that is not all! Oh no, that is not all…”

Solution

I will build this solution in 8 steps:

  1. Describe the cube.
  2. Create a MediaElement that plays video continuously.
  3. Map the MediaElement on to each of the six faces of the cube.
  4. Define the light source.
  5. Define the camera and bind the LookDirection to a function of its Position.
  6. Describe the path over which we will animate the Position.
  7. Apply the Point3DAnimationUsingPath to the Position property of the Camera
  8. Put it all together.

Describe the Cube

We describe the cube as a MeshGeometry3D and place it in a GeometryModel3D. This can then be placed inside a ModelVisual3D which will be added to the Viewport3D.

In the MeshGeometry3D we define the Positions and TriangleIndices as follows:

   <GeometryModel3D.Geometry>

      <MeshGeometry3D Positions=

               "-1,-1,-1    1,-1,-1   1, 1,-1   -1, 1,-1

                -1,-1,-1   -1, 1,-1  -1, 1, 1   -1,-1, 1  

                 1,-1,-1   -1,-1,-1  -1,-1, 1    1,-1, 1

                 1, 1,-1    1,-1,-1   1,-1, 1    1, 1, 1   

                 1,-1, 1   -1,-1, 1  -1, 1, 1    1, 1, 1

                -1, 1,-1    1, 1,-1   1, 1, 1   -1, 1, 1"

      TriangleIndices=

               "3,2,1       1,0,3

                7,6,5       5,4,7

                11,10,9     9,8,11

                15,14,13    13,12,15

                19,18,17    17,16,19

                23,22,21    21,20,23"

      </MeshGeometry3D>

   </GeometryModel3D.Geometry>

There are more than one ways to do this, but I chose the more verbose way in order to make it easier to define the mapping between the MediaElement and the faces of the cube.

In the Positions collection there are six lines, one for each face of the cube, four points for each face.

In the TriangleIndices there are six lines, one for each face of the cube, two triangles for each face. Note that the order of the points in the triangles is counterclockwise around the outfacing norm to the triangle’s plane.

Here is a diagram of the cube and its vertices:

3D Cube in XAML

Create the MediaElement

The MediaElement itself is not new, but this XAML snippet also shows how it is used as the Visual property of a VisualBrush that we will use to paint the Material of our GeometryModel3D.

   <GeometryModel3D.Material>

      <DiffuseMaterial>

         <DiffuseMaterial.Brush>

            <VisualBrush>

               <VisualBrush.Visual>

                  <MediaElement>

                     <MediaElement.Triggers>

                        <EventTrigger RoutedEvent="MediaElement.Loaded">

                           <EventTrigger.Actions>

                              <BeginStoryboard>

                                 <Storyboard>

                                    <MediaTimeline x:Name="mediaTimeline"

                                      RepeatBehavior="Forever" />

                                 </Storyboard>

                              </BeginStoryboard>

                           </EventTrigger.Actions>

                        </EventTrigger>

                     </MediaElement.Triggers>

                  </MediaElement>

               </VisualBrush.Visual>

            </VisualBrush>

         </DiffuseMaterial.Brush>

      </DiffuseMaterial>

   </GeometryModel3D.Material>

This MediaElement has a MediaTimeline that plays forever. There is something missing, of course - the Uri identifying the source of the media. But, no fear, we will set that in code. That’s why the MediaTimeline has been given a name.

Map the MediaElement on to the Cube

There are two parts to this mapping.

The first part, completed in the previous step, is to set the Visual property of the VisualBrush for the Material. The second part is to provide a relative coordinate in the 2D space of the VisualBrush for each 3D coordinate in the Positions collection. I defined the Positions collection in a consistent way for each face, so it turns out that the texture mapping is identical for each of the six faces.

We will add this XAML snippet to the properties of the MeshGeometry3D (after TriangleIndices, for example):

TextureCoordinates=

         "1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0

          1,1     0,1     0,0     1,0    

          1,1     0,1     0,0     1,0">

Define the Light Source

This one is simple. We will use an ambient white light to make sure that there is no shading on the cube.

   <ModelVisual3D>

      <ModelVisual3D.Content>

         <AmbientLight Color="White"></AmbientLight >

      </ModelVisual3D.Content>

   </ModelVisual3D>

Define the Camera

We will use a PerspectiveCamera in order to achieve the illusion of depth.

   <Viewport3D.Camera>

      <PerspectiveCamera

         x:Name="camera"

         UpDirection="0,0,1"

         LookDirection="{

               Binding RelativeSource={RelativeSource Self},

                       Path=Position,

                       Converter={StaticResource lookBackConverter}}"

         Position="0,0,0" />

   </Viewport3D.Camera>

The value of the Position property is meaningless because we are going to control this property with an animation. But, the LookDirection is interesting. In order to move the Camera and still look at the cube, we need to modify the look direction as the Position changes. This is how:

Camera.LookDirection = CenterPointOfInterest – Camera.Position

We could choose to animate the LookDirection concurrently with the Position property, but I chose a simpler, and in my opinion, a more elegant way.

I defined a converter from Camera.Position to Camera.LookDirection and apply it to a binding of LookDirection to Position.

This is the code for the converter:

    class LookBackConverter : IValueConverter

    {

        #region IValueConverter Members

 

        public object Convert(

            object value, Type targetType,

            object parameter,

            System.Globalization.CultureInfo culture)

        {

            return new Point3D(0,0,0) - (Point3D)value;

        }

 

        public object ConvertBack(

            object value, Type targetType,

            object parameter,

            System.Globalization.CultureInfo culture)

        {

            return null;

        }

 

        #endregion

    }

We could expose the center of interest as a public property of the LookBackConverter and set it in XAML, but for this demo, I will just assume its the origin of the coordinate system.

Describe the Path over which we will Animate the Position

You will recall from the previous post that Point3DAnimationUsingPath is lacking in that it doesn’t accept a description of a path in 3D space, but rather a constant Z coordinate and a path in 2D space.

So, we need to define a circle around the Z axis, with a diameter larger than the side of the cube, but not too much larger. The length of each side of the cube is 2 units, so 4 will do.

Unfortunately there is no EllipseGeometry for 2D, so I created my own with 2 ArcSegments. Here is the XAML:

   <PathGeometry x:Key="circlePath">

      <PathGeometry.Figures>

         <PathFigure StartPoint="-4, -4" IsClosed="False">

            <ArcSegment Point="4,4" Size="4, 4" />

            <ArcSegment Point="-4,-4" Size="4,4" />

         </PathFigure>

      </PathGeometry.Figures>

   </PathGeometry>

Apply the Point3DAnimationUsingPath to the Camera Position

Armed with the Point3DAnimationUsingPath and the circlePath PathGeometry it is now easy to define the animation we need:

   <Viewport3D.Triggers>

      <EventTrigger RoutedEvent="FrameworkElement.Loaded">

         <BeginStoryboard>

            <Storyboard>

               <local:Point3DAnimationUsingPath

                   Storyboard.TargetName="camera"

                   Storyboard.TargetProperty="Position"

                   Duration="0:0:20"

                   Z="2"

                   PathGeometry="{StaticResource circlePath}"

                   RepeatBehavior="Forever">

               </local:Point3DAnimationUsingPath>

            </Storyboard>

         </BeginStoryboard>

      </EventTrigger>

   </Viewport3D.Triggers>

As you can see, the camera named “camera” is the animation target and Position is the name of the target property. We set the circlePath PathGeometry as the PathGeometry and Z=2 so we rotate around the cube, but above it.

Putting It All Together

Here is the entire XAML for the solution:

<Window x:Class="RotatingCamera.Window1"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:RotatingCamera"

       Title="Window1"

       Width="500"

       Height="400"

       Loaded="Window_Loaded">

   <Grid>

      <Viewport3D>

         <Viewport3D.Resources>

            <local:LookBackConverter x:Key="lookBackConverter" />

            <PathGeometry x:Key="circlePath">

               <PathGeometry.Figures>

                  <PathFigure StartPoint="-4, -4" IsClosed="False">

                     <ArcSegment Point="4,4" Size="4, 4" />

                     <ArcSegment Point="-4,-4" Size="4,4" />

                  </PathFigure>

               </PathGeometry.Figures>

            </PathGeometry>

         </Viewport3D.Resources>

         <Viewport3D.Triggers>

            <EventTrigger RoutedEvent="FrameworkElement.Loaded">

               <BeginStoryboard>

                  <Storyboard>

                     <local:Point3DAnimationUsingPath

                         Storyboard.TargetName="camera"

                         Storyboard.TargetProperty="Position"

                         Duration="0:0:20"

                         Z="2"

                         PathGeometry="{StaticResource circlePath}"

                         RepeatBehavior="Forever">

                     </local:Point3DAnimationUsingPath>

                  </Storyboard>

               </BeginStoryboard>

            </EventTrigger>

         </Viewport3D.Triggers>

         <Viewport3D.Camera>

            <PerspectiveCamera

               x:Name="camera"

               UpDirection="0,0,1"

               LookDirection="{

                     Binding RelativeSource={RelativeSource Self},

                             Path=Position,

                             Converter={StaticResource lookBackConverter}}"

               Position="0,0,0" />

         </Viewport3D.Camera>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <AmbientLight Color="White"></AmbientLight >

            </ModelVisual3D.Content>

         </ModelVisual3D>

         <ModelVisual3D>

            <ModelVisual3D.Content>

               <GeometryModel3D>

                  <GeometryModel3D.Geometry>

                     <MeshGeometry3D Positions=

                             "-1,-1,-1    1,-1,-1   1, 1,-1   -1, 1,-1

                               -1,-1,-1   -1, 1,-1  -1, 1, 1   -1,-1, 1  

                                1,-1,-1   -1,-1,-1  -1,-1, 1    1,-1, 1

                                1, 1,-1   1,-1,-1    1,-1, 1    1, 1, 1   

                                1,-1, 1   -1,-1, 1  -1, 1, 1    1, 1, 1

                               -1, 1,-1   1, 1,-1    1, 1, 1   -1, 1, 1"

                     TriangleIndices=

                             "3,2,1       1,0,3

                               7,6,5       5,4,7

                               11,10,9     9,8,11

                               15,14,13    13,12,15

                               19,18,17    17,16,19

                               23,22,21    21,20,23"

                     TextureCoordinates=

                             "1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0

                               1,1     0,1     0,0     1,0    

                               1,1     0,1     0,0     1,0">

                     </MeshGeometry3D>

                  </GeometryModel3D.Geometry>

                  <GeometryModel3D.Material>

                     <DiffuseMaterial>

                        <DiffuseMaterial.Brush>

                           <VisualBrush>

                              <VisualBrush.Visual>

                                 <MediaElement>

                                    <MediaElement.Triggers>

                                       <EventTrigger RoutedEvent="MediaElement.Loaded">

                                          <EventTrigger.Actions>

                                             <BeginStoryboard>

                                                <Storyboard>

                                                   <MediaTimeline x:Name="mediaTimeline"

                                                     RepeatBehavior="Forever" />

                                                </Storyboard>

                                             </BeginStoryboard>

                                          </EventTrigger.Actions>

                                       </EventTrigger>

                                    </MediaElement.Triggers>

                                 </MediaElement>

                              </VisualBrush.Visual>

                           </VisualBrush>

                        </DiffuseMaterial.Brush>

                     </DiffuseMaterial>

                  </GeometryModel3D.Material>

               </GeometryModel3D>

            </ModelVisual3D.Content>

         </ModelVisual3D>

      </Viewport3D>

   </Grid>

</Window>

and this is what my cube looked like in one of my orbits around it.

Point3DAnimationUsingPath

Summary

In this post I demonstrated the use of my Point3DAnimationUsingPath to animate the Position of a Camera around a 3D object.

Its amazing how with WPF you can have so much going on in parallel, video, animations and databinding. Like the Cat in The Hat, really.

Published Monday, June 22, 2009 12:32 AM by David Sackstein

Comments

# re: Animating the Position of a 3D Camera in WPF

Wednesday, June 24, 2009 12:31 PM by Max Sackstein

Very impressive!

Can you program a cube like that into Powerpoint?

# Mapping MediaElement onto a Sphere with WPF

Monday, June 29, 2009 11:02 PM by David Sackstein's Blog

In my previous post I created a 3D cube, mapped a MediaElement onto each of its faces and animated the

# re: Animating the Position of a 3D Camera in WPF

Friday, December 04, 2009 9:17 AM by 邢伟

里面提示需要URI是怎么回事?

# re: Animating the Position of a 3D Camera in WPF

Thursday, September 02, 2010 12:30 AM by Orrie Pelc

Thanks so much! For my project, I needed to animate the camera Position and LookDirection independently, so I cloned the Point3DAnimationUsingPath class and made Vector3DAnimationUsingPath, and used both. (I also needed to animate across Y and Z axes, but not X, so I shuffled a variable or two around, and it worked just fine!) Thanks for creating this entry!!!

# re: Animating the Position of a 3D Camera in WPF

Monday, February 07, 2011 5:02 AM by jorge

where i could put the converter. . i am just a beginner. .

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems