Charles Petzold



A Rotating Wireframe Cube for Silverlight 3

July 26, 2009
Roscoe, N.Y.

It took awhile, but I managed to make a rotating wireframe cube using the new PlaneProjection class in Silverlight 3:


RotatingWireframeCube.html

The cube has perspective (that is, the rear is smaller than the front, and as it rotates, these sizes change) but something funny is going on with the lines, and they often shrink in thickness to practically nothingness. Unfortunately, this effect messes up the visual cues we use to separate froreground from background, and if you're not careful, you may start seeing something that looks like an slightly irregular hexahedron rather than a cube.

I won't post the project because the whole thing was done in XAML:

<UserControl 
        x:Class="RotatingWireframeCube.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Resources>
        <Style TargetType="Rectangle" x:Key="rectangleStyle">
            <Setter Property="Width" Value="200" />
            <Setter Property="Height" Value="200" />
            <Setter Property="Stroke" Value="Blue" />
            <Setter Property="StrokeThickness" Value="4" />
        </Style>
    </UserControl.Resources>
    
    <Grid x:Name="LayoutRoot">
        <Grid HorizontalAlignment="Center"
              VerticalAlignment="Center">
            <Rectangle Style="{StaticResource rectangleStyle}">
                <Rectangle.Projection>
                    <PlaneProjection x:Name="front"
                                     LocalOffsetZ="100"
                                     RotationX="30" />
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Style="{StaticResource rectangleStyle}">
                <Rectangle.Projection>
                    <PlaneProjection x:Name="top"               
                                     LocalOffsetZ="100" 
                                     RotationX="-60" />
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Style="{StaticResource rectangleStyle}">
                <Rectangle.Projection>
                    <PlaneProjection x:Name="bottom"
                                     LocalOffsetZ="100" 
                                     RotationX="120" />
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Style="{StaticResource rectangleStyle}">
                <Rectangle.Projection>
                    <PlaneProjection x:Name="back"
                                     LocalOffsetZ="-100"
                                     RotationX="30" />
                </Rectangle.Projection>
            </Rectangle>
        </Grid>
    </Grid>
    
    <UserControl.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="front"
                                     Storyboard.TargetProperty="RotationY"
                                     By="360" Duration="0:0:10"
                                     RepeatBehavior="Forever" />

                    <DoubleAnimation Storyboard.TargetName="top"
                                     Storyboard.TargetProperty="RotationY"
                                     By="360" Duration="0:0:10"
                                     RepeatBehavior="Forever" />

                    <DoubleAnimation Storyboard.TargetName="bottom"
                                     Storyboard.TargetProperty="RotationY"
                                     By="360" Duration="0:0:10"
                                     RepeatBehavior="Forever" />

                    <DoubleAnimation Storyboard.TargetName="back"
                                     Storyboard.TargetProperty="RotationY"
                                     By="360" Duration="0:0:10"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </UserControl.Triggers>
</UserControl>

As you can see, the cube consists of four rectangles (with names "front," "top," "bottom," and "back") that are basically drawn on top of each other and then shifted into place using PlaneProjection to form the cube. The front one is moved (conceptually speaking) 100 pixels closer to the user. The top one is also, and then rotated –90 degrees around the X-axis. The bottom one is the same but rotated a positive 90 degrees. The back one is moved 100 pixels away from the user. In addition, all the sides are rotated an additional 30 degrees around the X-axis to make the rotation more interesting. The animations then rotate each of the rectangles around the Y-axis.

It's really rather simple, so why did it take me 4 hours to get it right? Part of it was a learning experience, of course. I started out using the Matrix3DProjection and abandoned that because it wasn't doing quite what I wanted. (I'll have to investigate this more fully, but I believe that the OffsetZ member of Matrix3D is limited to values between 0 and 1.) So I switched to the PlaneProjection class, which is probably wonderful for simple flips and such, but it's not really built for composite transforms. For example, rotation around the X-axis is applied before rotation around the Y-axis, and you can't change that.

The major discovery I made — and in retrospect I should have expected this, but I had to try it anyway — is that Projection values aren't nestable. I first thought that I would build the cube in a Grid and then apply an animation to the Projection object of the Grid. This doesn't work. The flattening of the projected image to the XY plane occurs on the element level, so what you end up animating is the 2D view, not the 3D coordinates.