msdnmagazine.com February 2014 79
You can experiment with this with the
CircularText program. Create a 2D rotation
matrix in the CreateWindowSizeDependent-
Resources method sometime before the
Polygon coordinates are manipulated:
Matrix3x2F tiltMatrix = Matrix3x2F::Rotation(-8);
Th at’s a rotation of -8 degrees, and the
negative sign indicates a counterclockwise
rotation. In the inner loop, aft er x, y, and z
have been calculated, apply that transform
to the z and y values as if they were x and y values:
D2D1_POINT_2F tiltedPoint =
tiltMatrix.TransformPoint(Point2F(z, y));
z = tiltedPoint.x;
y = tiltedPoint.y;
Figure 5 shows what you’ll see.
Th is is much better, but it still has issues. Ugly things happen when
the geometry overlaps, and there’s nothing to suggest which part of
the geometry is nearer to you and which is further away. Stare at it,
and you might experience some perspective shift.
Still, the ability to apply 3D transforms to this object suggests
that it might also be easy to rotate the object around the Y axis—
and it is. If you imagine viewing the origin from the positive Y axis,
you’ll see that the X and Z axes are oriented the same way as the X
and Y axes in a 2D coordinate system.
The SpinningCircularText project implements two rotation
transforms to spin the text and tilt it. All the computational logic
previously in CreateWindowSizeDependentResources has been
moved into the Update method. Th e 3D points are rotated twice:
once around the X axis based on elapsed time, and then around
the Y axis based on the user sweeping a fi nger up and down the
screen. Th is Update method is shown in Figure 6.
It’s well-known that composite matrix transforms are equivalent
to matrix multiplications, and because matrix multiplications
aren’t commutative, neither are composite transforms. Try switch-
ing around the application of the tilt and rotate transforms for a
diff erent eff ect (which you might actually prefer).
When creating the SpinningCircularText program, I adapted
the SampleFpsTextRenderer class created by the Visual Studio
template to create the SpinningCircularTextRenderer class, but I
left the display of the rendering rate. Th is allows you to see how bad
the performance is. On my Surface Pro, I see a frames per second
(FPS) fi gure of 25 in Debug mode, which indicates the code is not
keeping up with the refresh rate of the video display.
If you don’t like that performance, I’m afraid I have some bad
news: I’m going to make it even worse.
Separating Foreground from Background
Th e biggest problem with the path geometry approach to 3D is the
eff ect of overlapping areas. Is it possible to avoid those overlaps? Th e
image this program is attempting to draw is not all that complex. At
any time, there’s a front view of part of the text and a back view of
the rest of the text, and the front view should always be displayed
on top of the back view. If it were possible to separate the path
geometry into two path geometries—one for the background and
one for the foreground—you could render those path geometries
with separate FillGeometry calls so the fore-
ground would be on top of the background.
Th ese two path geometries could even be
rendered with diff erent brushes.
Consider the original path geometry cre-
ated by the GetGlyphRunOutline method.
Th at’s just a fl at 2D path geometry occupying
a rectangular area. Eventually, half of that
geometry is displayed in the foreground,
and the other half is displayed in the back-
ground. But by the time the Polygon objects are obtained, it’s too
late to make that split with anything like computational ease.
Instead, the original path geometry needs to be broken in half
before the Polygon objects are obtained. Th is break is dependent
on the rotation angle, which means that much more logic must be
moved into the Update method.
Th e original path geometry can be split in half with two calls
to the CombineWithGeometry method. Th is method combines
two geometries in various ways to make a third geometry. Th e two
geometries that are combined are the original path geometry that
describes the text outlines and a rectangle geometry that defi nes
a subset of the path geometry. This subset appears in either the
foreground or background, depending on the rotation angle.
For example, if the rotation angle is 0, the rectangle geometry
must cover the central half of the path geometry of the text out-
lines. Th is is the part of the original geometry that appears in the
foreground. Calling CombineWithGeometry with the D2D1_
COMBINE_MODE_INTERSECT mode returns a path geometry
consisting only of that central area, while calling CombineWith-
Geometry with D2D1_COMBINE_MODE_EXCLUDE obtains
a path geometry of the remainder—the parts on the left and right.
These two path geometries can then be converted to Polygon
objects separately for manipulation of the coordinates, followed
by a conversion back to separate path geometries for rendering.
Th is logic is part of a project named OccludedCircularText, which
implements the Render method by fi lling the two geometries with
diff erent brushes, as shown in Figure 7.
Now it’s much more obvious what’s in the foreground and what’s
in the background. Yet, so much computation has been moved to
the Update method that performance is very poor.
In a conventional 2D programming environment, I would’ve
exhausted all the 2D programming tools at my disposal and now
be stuck with this terrible performance. Direct2D, however, off ers
an alternative approach to rendering the geometry that simplifi es
the logic and improves performance immensely. Th is solution
makes use of the most basic 2D polygon—which is a polygon that
also plays a major role in 3D programming.
I speak, of course, of the humble triangle. Q
CHARLES PETZOLD is a longtime contributor to MSDN Magazine and the author
of “Programming Windows, 6th edition” (O’Reilly Media, 2012), a book about
writing applications for Windows 8. His Web site is charlespetzold.com.
THANKS to the following technical expert for reviewing this article:
Jim Galasyn (Microsoft )
Figure 7 The OccludedCircularText Display