Telerik blogs

Hello everyone,

Since that is my first blog, I suppose a short introduction won't do a harm. My name is Evtim Papushev and I am Software Developer @ Telerik. I'm in the Charting division and I currently work on the new RadChart for WPF. Well, that's enough about me, now let's move to the today's topic.

 

Placing labels in a 3-dimensional scene isn't an issue at all. Well, at least technically, it is simple - place a 3D model, then set text box as diffuse material, set the texture coordinates and you are ready to roll.

What such a solution lacks is business application. I will mention a couple of problems:

  • These labels are placed as textures, meaning that they go through the process of stretching, which distorts the final image presented to the client:

StretchedLabel

  • Applying rotations and scale makes the labels even less readable (or invisible when rotated near 90 degrees):

RotatedLabel

  • Being in a 3D scene the label might happen to be partially hidden by other objects, which again decreases readability.

HiddenLabel

 

"So what do you suggest?" you might ask. Well, two approaches come to my mind:

  • Place 3D labels and make them always look at the direction of the camera (which eliminates only one of the aforementioned problems).
  • Place regular labels (say text boxes) in a Canvas or Adorner on top of the Viewport3D.

 

Both approaches include finding the total transformation matrix. That is accomplished by traversing the visual tree from the current node to the Viewport3D, retrieving the transformations at each level. If you don't feel like writing that code, take a look at http://www.charlespetzold.com/3D/, and more precisely the Petzold.Media3D.dll library. Note, that there are restrictions on its usage.

 

For the first approach you just need to "extract the rotation" from the total transformation matrix and find the inverse:

public static Matrix3D InverseRotationMatrix(const Matrix3D totalTransformation)

{

    return new Matrix3D(
        totalTransformation.M11, totalTransformation.M21, totalTransformation.M31, 0,
        totalTransformation.M12, totalTransformation.M22, totalTransformation.M32, 0,
        totalTransformation.M13, totalTransformation.M23, totalTransformation.M33, 0,
        0, 0, 0, 1).Invert();

}

I will not go into detail about which part of the matrix describes what; why and when the inverse matrix exists, etc.You just have to blindly trust me it exists or read say that article (or any other on the topic).

 

The second approach needs extending the algorithm for finding the total transformation matrix to include the Camera and Viewport transformations (here you might get non-invertable matrix, but that's another as we do not need the Inverse). In this case, instead of placing 3D label, place some simple object. You just need one Point3D, which you transform by the total transformation matrix. From the result (again Point3D) just take the X and Y values and you have the Point where the initial Point3D will render. That, obviously, is the position in the Canvas/Adorner where you should place the label's TextBlock (don't forget to properly align it, though).

 

This approach has an obvious drawback. The label will be visible all the time, even when it should be hidden. That could be fixed by placing invisible 3D objects (say Tetrahedron with empty diffuse material) at the places where the labels should be (in the 3D scene). Then, since the label position in the 2D space is already known(see the previous paragraph), do a hit test against those 2D positions and check if the corresponding 3D objects are infront (i.e. the label should be visible if the respective 3D object is first in the hit-test).

 

Obviously, both approaches have drawbacks. The non-obvious of the second approach that I should mention is the overhead of doing a hit test for each label (by say using VisualTreeHelper.HitTest). But since it comes out of the box, we are free to abuse it ...

 

Feel free to comment.


Comments

Comments are disabled in preview mode.