WPF Elliptical Layout Control – 3D!

After finishing my last post, WPF Elliptical Layout Control, I sat down and wondered what to do next. It occurred to me that creating a 3D carousel in WPF is a common question and one that doesn’t have all that many examples. A 3D control is also a natural progression from the 2D control and is not all that different. All we need to do is layout the objects in 2D, then rotate those points according to the orientation of the imaginary layout ellipse, taking in to account depth.

This example creates a new control which derives from FrameworkElement and is very similar to the implementation of the Viewport3D control. The new control overrides the measure and layout methods of FrameworkElement and positions the child controls (supplied as a collection of UIElements) according to the orientation of an imaginary 2D ellipse in 3D space.

It is assumed that the reader has some knowledge of custom controls, LINQ and 3D in WPF. The code provided is by no means an ideal implementation of the theory, it should be thought of more as a quick demonstration of the concept. And remember, in WPF there is almost always more than one way of doing the same thing, mine is just my take on the problem :0)

3D Elliptical Layout Panel Demo
3D Elliptical Layout Panel Demo

The Idea

In the last example, we worked out how to lay out controls as if they were positioned at equal intervals around the edge of an imaginary ellipse. We now want to take that ellipse from a 2D space in to 3D, giving a sense of depth in the control.

Taking the existing layout logic in to the third dimension is fairly trivial if we remember that the positions we calculate for child controls in the 2D version could be thought of as being 3D points, all at the same distance away from the screen, i.e. z = 0. So by treating the points that are generated as 3D points, each with a value of zero for z (their depth), we can then rotate them to match the orientation of the layout ellipse (defined by an x, y and z axis rotation).

In short, we calculate the position of each child element as before, then rotate that point in 3D to match the rotation of a virtual layout ellipse. The layout ellipse will be configured as before, although this time we need to allow a way to configure its orientation. This is as simple as adding three new properties to allow an angle of rotation to be specified for each axis.

When it comes to the control, we need to look to 3D controls in WPF. We could create our own control which lays out its content, then projects it in to 3D and renders the control scaled such that they appear to be 3D, but that’s no fun and way too much effort! It would somehow be great if we could take advantage of the relatively new Viewport2DVisual3D control, effictively a 3D wrapper for a UIElement. It would also be great if the control exposed just a collection of UIElements, which internally it could wrap in a Viewport2DVisual3D control. 

Now we know what our items are doing, we need to think of a suitable container. If we look at the Viewport3D control, we can see that it wraps a Viewport3DVisual, exposing a collection of UIElement objects which it implicitly converts to Visual3D objects and adds to the inner Viewport3DVisual, as well as one or two other things. This is very similar to what we want to do, with the difference that we want to also define the layout mechanism too.

The Theory

I guess the first thing to tackle here is how to take the generated 2D position for each child control and turn that in to a 3D point, which we can use to create a TranslateTransform3D. For each point we generate on the ellipse, create a Point3D object using the x and y values of the 2D point and supply a z value of 0d (or whatever you want for the ellipse’s distance from the image plane).

Next, rotate the point around each axis using the rotations used to describe the layout ellipse’s orientation.  If you’re familiar with 3D graphics you may well be aware of Gimbal lock, something we want to avoid when we’re rotating the child control positions. To combat this, the rotation method will use quaternions. If you’ve not come across quaternions before, I recommend a quick look on Google as there are far better explanations out there than I can give. Also look at this site which gives the theory behind the math used to rotate a point.

private readonly static Vector3D UnitXAxis3D = new Vector3D(1d, 0d, 0d);
private readonly static Vector3D UnitYAxis3D = new Vector3D(0d, 1d, 0d);
private readonly static Vector3D UnitZAxis3D = new Vector3D(0d, 0d, 1d);

private static Point3D RotatePoint3D(Point3D point, double xRotation, double yRotation, double zRotation)
{
    Quaternion xQ = new Quaternion(UnitXAxis3D, xRotation);
    Quaternion yQ = new Quaternion(UnitYAxis3D, yRotation);
    Quaternion zQ = new Quaternion(UnitZAxis3D, zRotation);

    Quaternion xyzQ = xQ * yQ * zQ;
    Quaternion xyzQc = xyzQ;
    xyzQc.Conjugate();

    Quaternion pQ = new Quaternion(point.X, point.Y, point.Z, 0d);
    Quaternion q = xyzQ * pQ * xyzQc;
    Point3D rotatedPoint = new Point3D(q.X, q.Y, q.Z);

    return rotatedPoint;
 }

With the Point3D in hand, a TranslateTransform3D object can be created and applied to the child Viewport2DVisual3D control. This leads to the next question of how we are going to handle the wrapping of UIElements. With the addition of the Viewport2DVisual3D control, it is now possible to easily use a UIElement in a 3D scene, maintaining all the usual input handling, so no more messing around with visual brushes and models! You don’t get off too lightly though as you still need to provide the Viewport2DVisual3D with some information. This includes a Geometry3D object detailing a mesh that defines the surface which the inner Visual is to be rendered on; the material which should be used when rendering the Visual on the mesh and finally the Visual object itself.

To keep things simple, we can define a basic mesh containing a unit square. This mesh is going to be used as the geometry for each control surface, so we need a way of sizing it according to the dimensions of the control being rendered. This is again a trivial problem and is solved by creating a ScaleTransform3D object which scales the Viewport2DVisual3D control so that it is scaled by the dimensions of the inner Visual object, ignoring z. Nice!

The scale transform is created for each Viewport2DVisual3D control each time the MeasureOverride method is called. There is no particular reason to place this code here, other than it keeps the sizing logic separate from the layout logic, keeping things a bit better organised. The next code snippet is included to highlight the use of the OfType<T>() LINQ extension method, used on the Children property of the control’s inner Viewport3DVisual. This method returns an IEnumerable<T> object containing all the items in the collection with a type specified by T. Because we know that our Children collection contains a light somewhere within it, we want to skip over that when scaling the child objects.

protected override Size MeasureOverride(Size availableSize)
{
    //Iterate all the children of the inner viewport.
    foreach (Viewport2DVisual3D visualChild 
        in viewport3DVisual.Children.OfType())
    {
        //Get the inner UIElement
        UIElement element = visualChild.Visual as UIElement;
        //Create a scale transform so that the control appears the right size
        ScaleTransform3D scaleTransform
            = new ScaleTransform3D(element.DesiredSize.Width, element.DesiredSize.Height, 0d);

        //Add the scale transform in to the Viewport2DVisual3D's transform group.
        AssertTransform3D(scaleTransform, visualChild);
    }

    return availableSize;
}

The container that will hold all of these Viewport2DVisual3D controls will need to be a Viewport3DVisual. Again because we are dealing with 3D we need to define a few extra things for the Viewport3DVisual. The first is a Camera object which defines how controls displayed in the Viewport3DVisual appear to the viewer. The second is a Light object which defines an AmbientLight object used to provide a uniform white light in the 3D scene (otherwise everything would be dark and you’d see nothing more than a black screen).

To make the controls appear to be positioned within 3-dimensional space, we will use a PerspectiveCamera, meaning objects which are placed deeper in to the control (further from the screen) will appear smaller than those which are closer to the screen. All we need to do to configure the camera is tell it where it is and the direction it is looking. Because we are keeping things simple here, we’ll position our camera somewhere along the positive z-axis, and have it looking down the z-axis (towards the origin). This means our camera will be looking straight on at the objects placed within the control, with the layout ellipse’s origin at the origin of the scene.

The Solution

Here I present a simple demonstration of the above in the attached solution. The solution contains a simple Window which hosts the custom 3D ellipse layout control. The ellipse layout control is included in the ElliptiseLayout3DPanel class and adds several dependency properties and a few methods to the FrameworkElement class.

EllipticalLayoutPanel3D Class Diagram
EllipticalLayoutPanel3D Class Diagram

The main window also contains a few controls which allow you to interact with the layout control, allowing you to specify the size, location and pose of the layout control as well as add or remove items to the control. There are also two buttons will moves the controls around the ellipse, one at a time. This is a bit of a jig and is as simple as removing an item from the top of the collection held in the control’s Children property and adding it to the end of the collection. The reverse is true for moving back through the items. The light sometimes gets caught up in this, so you may not see anything happen for a click or two when switching direction.

With the default orientation of the ellipse when the demo app first runs, changes made to the z-rotation slider will have the effect of spinning the controls around, in the fashion of a carousel. This could be used to animate the rotation items in the control.

To the right is a class diagram, showing the structure of the layout control. You can see it’s pretty simple and doesn’t need to add much to its FrameworkElement ancestor. 

Below is a screen-shot of the demo application in action. The values of the sliders are directly bound to the panel’s dependency properties, meaning the only code-behind is for the four button event handlers.

 

3D Elliptical Layout Panel Demo
3D Elliptical Layout Panel Demo

The code is available for download as a Visual Studio 2008 (SP1) solution, built against .Net 3.5 SP1.

Going Forwards

The light source and other properties of the control are not configurable. You may want to expose the lights as a separate collection, or at least allow the colour to be altered.

The control will need some more work to enable it to be used in an ItemsPanelTemplate, so it won’t work out of the box.

You may want to add proper methods for controlling the order of items in the control, possibly animating this.

All and any comments / bugs / suggestions are welcomed!

23 thoughts on “WPF Elliptical Layout Control – 3D!

  1. First of congrats, this a good basic 3D example, I learn many things from the code and from the description also………. thank you very much for sharing.

    Well, the thing is, and don’t know if its a bug, that I could use the control inside of a “WPF User Control Library” I debugged the code and it seems that I can refer to the “RDLocal.xaml” dictionary from the “EllipseLayout3DPanel.cs” class if the project is a control library.

    I’m kind of new in these WPF topic so if anyone can help me with this problem I’ll be very happy.

  2. @Mark

    Thanks! Glad you like it.

    @Lazlo

    Thanks for the comments, it’s great to hear the post was useful.

    I have just created a test User Control Library with the EllipseLayout3DPanel in it, and didn’t run in to any issues. When you placed the code for the layout control in the control library, did you also copy across the resource dictionary?

    You wouldn’t be able to reference a resource dictionary in the project which is using the Control Library containing the panel as this would create a circular reference between the two projects. Project A will reference Control Library A for the layout control, but Control Library A will then reference Project A for the resource dictionary.

    There is no reason why you can’t define the content of the resource dictionary in code, I only used a resource dictionary as its content is used by a few projects.

    Does that make things any clearer?

  3. @Mark
    Thank you very much for the prompt response.

    You were right my problem had a lot to do with the references to the dictionaries, now I have the results that I was looking for, again thank you very much for the article and for the help

    @Lazlo

  4. Hi, I am getting this error in blend
    Error 3 Assembly ‘EllipseLayout3D’ was not found. Verify that you are not missing an assembly reference. Also, verify that your project and all referenced assemblies have been built

    Also, I want to make each item of the carousel Clickable and should be able to navigate from that page to another page,
    Please help

  5. Hi! Nice and clean example!

    Do you think it’s Possible to implement the following? :

    On Mouse drag of element in Ellipse to either Left or Right, the Ellipse turns accordingly.
    I’m Kind of new to WPF and dunno where to start to implement this

    Thanks
    Daniel

  6. Very nice.

    There seems to be a small problem when you have transparent contents/children. I am adding buttons with custom styles with round edges but when item/button is on the left hand side the round edges show white background. Any idea why that would be happening???
    Thanks.

  7. The code is nice but it has issues with the object corners.

    I think this is due to the order they are being rendered, its very problematic if you are using translucent controls.

    Anyone one know how to fix this issue?

  8. Hey guys / gals,

    Sorry, it has been a while 😉 I’ve noticed this page still gets quite a few views so later today I’ll try and get the code update to .Net 4.0 VS 2010 and address the issues you have mentioned.

    Cheers,

    Sam.

  9. anyone know for the EllipseRotationZ, how can I put the 0 degree’th point to be in the front(nearest to camera) instead of at the back(furthest from camera)? so that the first item is at the front while i add more items.. thanks

Leave a Reply to Thiwa Cancel reply

Your email address will not be published. Required fields are marked *