(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Navigation in 3D world of 2D objects

(74 votes)
Miroslav Miroslavov
>
Miroslav Miroslavov
Joined Nov 24, 2009
Articles:   8
Comments:   6
More Articles
4 comments   /   posted on May 31, 2010
Categories:   White Papers , General

This article is compatible with the latest version of Silverlight.

This is part 7 of the series “Silverlight in Action”.

Here we’re sharing our experience from the amazing CompletIT web site.

Introduction

It’s always complicated, when you have to deal with 3D objects. No matter what environment and framework you choose, you’ll need to learn a lot of complicated structures and procedures in order to implement even a simple scenario. But the bigger problem in Silverlight is the lack of “native” support for 3D (those helpful frameworks). There are some available frameworks out there, but they tend to be very heavy (because of the complex nature of the 3D), so when you need good performance, you’d better to do everything on your own.

That’s why in this article, we’ll create a very simple 3D framework, that will allows us to position normal 2D objects like Grids, Buttons, Borders and TextBlocks in a 3D world and also allows us to move the camera(the viewer) all over the place.

A snapshot of the 3D world

Portfolio

Try the navigation by yourself.

Just click and drag to move the Camera and wheel the mouse to zoom.

Get Microsoft Silverlight

Implementation

We will implement a simple 3D abstraction. In order to do that, we will define a class to hold the 3 coordinates – X, Y, Z of each object, that will be placed on the 3D scene. This class will also define the Camera (the Viewer’s position) and will allow us to project each object on the screen based on the coordinates and the camera position. The actual projection will calculate the needed translation and scaling. You can read more information about the 3D projection here.

The ThreeD class

In order to make it possible to set on each element on the scene, x, y and z coordinates, lets define the corresponding attached properties. Also since the scene will support only one viewer (camera), we can define its coordinates as static double properties.

 public static readonly DependencyProperty XProperty =
     DependencyProperty.RegisterAttached("X", typeof(double), typeof(ThreeD), new PropertyMetadata(0.0));
  
  
 public static readonly DependencyProperty YProperty =
     DependencyProperty.RegisterAttached("Y", typeof(double), typeof(ThreeD), new PropertyMetadata(0.0));
  
  
 public static readonly DependencyProperty ZProperty =
     DependencyProperty.RegisterAttached("Z", typeof(double), typeof(ThreeD), new PropertyMetadata(0.0));
  
   
 public static double CameraX
 {
     get;
     set;
 }
 public static double CameraY
 {
     get;
     set;
 }
 public static double CameraZ
 {
     get;
     set;
 }
  
 public static double CenterScreenX
 {
     get;
     set;
 }
 public static double CenterScreenY
 {
     get;
     set;
 }
  
 public static double GetX(DependencyObject obj)
 {
     return (double)obj.GetValue(XProperty);
 }
  
 public static void SetX(DependencyObject obj, double value)
 {
     obj.SetValue(XProperty, value);
 }
  
 public static double GetY(DependencyObject obj)
 {
     return (double)obj.GetValue(YProperty);
 }
  
 public static void SetY(DependencyObject obj, double value)
 {
     obj.SetValue(YProperty, value);
 }
  
 public static double GetZ(DependencyObject obj)
 {
     return (double)obj.GetValue(ZProperty);
 }
  
 public static void SetZ(DependencyObject obj, double value)
 {
    obj.SetValue(ZProperty, value);
 }

After we can set 3D coordinates to our normal visuals, we’ll need a method to project them on the screen. In order to do that, lets first clarify the scene. The screen is with Z = 0 coordinate, so every object will have positive Z and the viewer will be able to “fly” through them. And here is the actual projection method.

 public static void Project(FrameworkElement element)
 {
     CompositeTransform transform = element.EnsureTransform();
  
    double x = GetX(element);
    double y = GetY(element);
    double z = GetZ(element);
 
    if (CameraZ <= z)
    {
         element.Visibility = Visibility.Collapsed;
    }
    else
    {
         element.Visibility = Visibility.Visible;
         double factor = CameraZ / (CameraZ - z);
  
         transform.TranslateX = (x - CameraX) * factor + CenterScreenX;
         transform.TranslateY = (y - CameraY) * factor + CenterScreenY;
  
         element.Opacity = transform.ScaleX = transform.ScaleY = Math.Abs(1 - factor);
    }
 }

Let me explain the code in a couple of words.

  • When the camera goes “behind” the object, we will collapse it.
  • Otherwise – calculate the factor of the distance between the camera to the screen and the camera to the object. After that (x – CameraX) * factor will give us the X offset. (can look at this wiki diagram to see it more clearly). And at the end we normalize the coordinates by adding the Center of screen offsets.
  • And the scaling is also very important for the real 3D effect. The Opacity is not obligatory, but can be added for more realistic effect.
The ThreeDPanel class

In order to make the camera live, lets create an attached behavior for a panel. This class will hold the logic of moving the camera, zooming and projecting the objects back to 2D. It’s as simple as moving the camera’s X, Y and Z coordinates responding to Mouse events.

 private static void FixZIndex(Panel panel)
 {
     int index = 0;
     foreach (UIElement child in panel.Children.OrderBy(ThreeD.GetZ))
     {
        Canvas.SetZIndex(child, ++index);
    }
 }
   
 private static void PanelSizeChanged(object sender, SizeChangedEventArgs e)
 {
     var panel = (Panel) sender;
   
     ThreeD.CameraX = ThreeD.CenterScreenX = e.NewSize.Width/2;
     ThreeD.CameraY = ThreeD.CenterScreenY = e.NewSize.Height/2;
  
     FixZIndex(panel);
     ProjectChildren(panel);
 }
   
 private static void PanelMouseMove(object sender, MouseEventArgs e)
 {
     var panel = (Panel) sender;
   
     if (isDragging)
     {
         Point currentPoint = e.GetPosition(panel);
         Move(panel, currentPoint);
     }
 }
   
 private static void PanelMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
 {
     var panel = (Panel) sender;
  
     isDragging = false;
     panel.ReleaseMouseCapture();
 }
  
 private static void PanelMouseWheel(object sender, MouseWheelEventArgs e)
 {
     var panel = (Panel) sender;
  
     Point zoomCenter = e.GetPosition(panel);
     Zoom(panel, zoomCenter, Math.Sign(e.Delta)*zoomChange);
 }
  
 private static void PanelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
     var panel = (Panel) sender;
   
     panel.CaptureMouse();
     isDragging = true;
     startDragPoint = e.GetPosition(panel);
 }
   
 private static void Move(Panel panel, Point currentPoint)
 {
     ThreeD.CameraX -= ((currentPoint.X - startDragPoint.X)/2);
     ThreeD.CameraY -= ((currentPoint.Y - startDragPoint.Y)/2);
  
     ProjectChildren(panel);
  
     startDragPoint = currentPoint;
 }
  
 private static void Zoom(Panel panel, Point zoomCenter, double deltaZoom)
 {
     ThreeD.CameraZ -= deltaZoom;
     ThreeD.CameraX += (zoomCenter.X - ThreeD.CameraX)*(deltaZoom/ThreeD.CameraZ);
    ThreeD.CameraY += (zoomCenter.Y - ThreeD.CameraY)*(deltaZoom/ThreeD.CameraZ);
   
     ProjectChildren(panel);
 }
   
 private static void ProjectChildren(Panel panel)
 {
     foreach (FrameworkElement element in panel.Children)
     {
         ThreeD.Project(element);
     }
 }

It’s also tricky to change the items Canvas.ZIndex property according to theirs Z coordinate (It’s done in the FixZIndex method).

The MainPage xaml

Using this simple 3D framework is as easy as setting the needed 3D coordinates and manipulating the camera. Have a look at the code and check yourself.

 <Canvas x:Name="LayoutRoot"
         Background="{StaticResource background}"
         ThreeDWorld:ThreeDPanel.IsThreeDPanel="True">
    <Grid ThreeDWorld:ThreeD.X="200"
          ThreeDWorld:ThreeD.Y="100"
          ThreeDWorld:ThreeD.Z="100"
          MaxWidth="300">
        <Border Background="Red" />
          <TextBlock Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam blandit mattis ullamcorper. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas" />
      </Grid>
   
     <Grid ThreeDWorld:ThreeD.X="100"
           ThreeDWorld:ThreeD.Y="250"
           ThreeDWorld:ThreeD.Z="50"
           MaxWidth="300">
          <Border Background="Green" />
          <TextBlock Text="Etiam porttitor leo at turpis mattis sed convallis tortor consequat. Donec vel sodales felis. Fusce euismod mollis aliquet. Morbi nec augue ipsum, non sodales risus. Fusce at sem vel diam congue vestibulum ut a enim. Praesent eget lorem vitae purus sagittis ultricies." />
     </Grid>
   
    <Grid ThreeDWorld:ThreeD.X="300"
          ThreeDWorld:ThreeD.Y="225"
          ThreeDWorld:ThreeD.Z="150"
          MaxWidth="300">
          <Border Background="Blue" />
          <TextBlock Text="Suspendisse sit amet nunc hendrerit enim iaculis bibendum. Sed ut metus nec felis congue interdum et at orci. Duis imperdiet, mi eget eleifend porta, dui lacus consequat tortor, ac euismod purus ipsum id purus. Sed sollicitudin elementum ultrices. Vestibulum hendrerit dolor justo." />
    </Grid>
   
    <Grid ThreeDWorld:ThreeD.X="400"
          ThreeDWorld:ThreeD.Y="125"
          ThreeDWorld:ThreeD.Z="125"
          MaxWidth="300">
         <Border Background="Yellow" />
          <TextBlock Text="Vivamus nulla tortor, ornare et commodo nec, gravida eget justo. Nunc dignissim, nibh a posuere auctor, libero odio pretium turpis, convallis placerat sem sapien eget lectus. Curabitur nulla sapien, mollis auctor egestas vitae, tincidunt et ante." />
    </Grid>
   
    <Grid ThreeDWorld:ThreeD.X="500"
          ThreeDWorld:ThreeD.Y="300"
          ThreeDWorld:ThreeD.Z="100"
          MaxWidth="300">
          <Border Background="Ivory" />
          <TextBlock Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam blandit mattis ullamcorper. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas" />
    </Grid>
   
    <Grid ThreeDWorld:ThreeD.X="500"
          ThreeDWorld:ThreeD.Y="175"
          ThreeDWorld:ThreeD.Z="140"
          MaxWidth="300">
          <Border Background="Gold" />
          <TextBlock Text="Suspendisse sit amet nunc hendrerit enim iaculis bibendum. Sed ut metus nec felis congue interdum et at orci. Duis imperdiet, mi eget eleifend porta, dui lacus consequat tortor, ac euismod purus ipsum id purus. Sed sollicitudin elementum ultrices. Vestibulum hendrerit dolor justo." />
    </Grid>
 </Canvas>

Conclusion

Sometimes is better for simple scenarios to have simple solutions, as that one. It is lightweight and performs well, as well as, is doing what it’s suppose to do – abstracting the complex 3D world and give us an easy way to create more beautiful User Interface. For more complex scenarios regarding 3D in Silverlight, you can look at the Kit 3D available at codeplex.

Download the code

Stay tuned for more articles from this series coming up next week.


Subscribe

Comments

  • -_-

    RE: Navigation in 3D world of 2D objects


    posted by Jay R on Jun 03, 2010 00:45
    doesnt work on linux..
  • -_-

    RE: Navigation in 3D world of 2D objects


    posted by Dennis on Jun 03, 2010 10:16
    There is a bug I've found - if you zoom in for a while (until the objects are no longer seen) and then zoom out, the objects will become stacked on top of each other in the top left corner. In that case, dragging will no longer work.
  • miromiroslavov

    RE: Navigation in 3D world of 2D objects


    posted by miromiroslavov on Jun 03, 2010 10:20

    Yep you're right. I should set the Camera movement constraints, to avoid unexpected behavior. (as in the original web-site.).

    Thank you for reporting it. :-)

  • -_-

    RE: Navigation in 3D world of 2D objects


    posted by Ray Akkanson on May 28, 2011 21:53

    What versions of Silverlight supported?

    Ray Akkanson

Add Comment

Login to comment:
  *      *       

From this series