Create mobile apps with HTML5, JavaScript and Visual Studio

(Elle) #1

msdnmagazine.com February 2014 77


positive Y axis, your thumb points to positive Z. With a left -hand


coordinate system, it’s the same except using the left hand.


My goal here is to obtain a 2D path geometry of a short text string,


and then twist it around the origin into a 3D ring so the beginning


meets the end, similar to the illustration shown in Figure 1. Because


I’ll be converting 2D coordinates to 3D coordinates and then back


to 2D, I’ve chosen to use a 3D coordinate system with Y coordinates


increasing going down, just like in 2D. Th e positive Z axis comes out


of the screen, but it’s really a left -hand coordinate system.


To make this whole job as easy as possible, I’ve used a font fi le


stored as a program resource, and created an IDWriteFontFile


object for obtaining the IDWriteFontFace object. Alternatively,


you could obtain an IDWriteFontFace through a more roundabout


method from the system font collection.


The ID2D1PathGeometry object generated from the Get-


GlyphRunOutline method is then passed through the Simplify method


using the D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES


argument to fl atten all Bézier curves into sequences of short lines. Th at


simplifi ed geometry is passed into a custom ID2D1GeometrySink


implementation named FlattenedGeometrySink to further decom-


pose all the straight lines into much shorter straight lines. Th e result


is a completely malleable geometry consisting only of lines.


To ease the manipulation of these coordinates, FlattenedGeometry-


Sink generates a collection of Polygon objects. Figure 2 shows the


defi nition of the Polygon structure. It’s basically just a collection of


connected 2D points. Each Polygon object corresponds to a closed


fi gure in the path geometry. Not all fi gures in path geometries are


closed, but those in text glyphs are always closed, so this structure


is fi ne for that purpose. Some characters (such as C, E and X) are


described by just one Polygon; some (A, D and O) consist of two


Polygon objects for the inside and outside; some (B, for example)


consist of three; and some symbol characters may have many more.


Among the downloadable code for this column is a Windows


Store program named CircularText that creates a collection of Poly-


gon objects based on the text “Text in an Infi nite Circle of,” where


the end is intended to connect back to the beginning in a circle. Th e


text string is actually specifi ed in the program as “ext in an Infi nite


Circle of T” to avoid a space at the beginning or end that would


disappear when a path geometry is generated from the glyphs.


Th e CircularTextRenderer class in the CircularText project con-


tains two std::vector objects of type Polygon called m_srcPolygons


(the original Polygon objects generated from the path geometry)


and m_dstPolygons (the Polygon objects used to generate


the rendered path geometry). Figure 3 shows the method


struct Polygon
{
// Constructors
Polygon()
{
}

Polygon(size_t pointCount)
{
Points = std::vector<D2D1_POINT_2F>(pointCount);
}

// Move constructor
Polygon(Polygon && other) : Points(std::move(other.Points))
{
}

std::vector<D2D1_POINT_2F> Points;

static HRESULT CreateGeometry(ID2D1Factory* factory,
const std::vector<Polygon>& polygons,
ID2D1PathGeometry** pathGeometry);
};

HRESULT Polygon::CreateGeometry(ID2D1Factory* factory,
const std::vector<Polygon>& polygons,
ID2D1PathGeometry** pathGeometry)
{
HRESULT hr;

if (FAILED(hr = factory->CreatePathGeometry(pathGeometry)))
return hr;

Microsoft::WRL::ComPtr<ID2D1GeometrySink> geometrySink;

if (FAILED(hr = (*pathGeometry)->Open(&geometrySink)))
return hr;

for (const Polygon& polygon : polygons)
{
if (polygon.Points.size() > 0)
{
geometrySink->BeginFigure(polygon.Points[0],
D2D1_FIGURE_BEGIN_FILLED);

if (polygon.Points.size() > 1)
{
geometrySink->AddLines(polygon.Points.data() + 1,
polygon.Points.size() - 1);
}
geometrySink->EndFigure(D2D1_FIGURE_END_CLOSED);
}
}
return geometrySink->Close();
}

Figure 2 The Polygon Class for Storing Closed Path Figures


void CircularTextRenderer::CreateWindowSizeDependentResources()
{
// Get window size and geometry size
Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();
float geometryWidth = m_geometryBounds.right - m_geometryBounds.left;
float geometryHeight = m_geometryBounds.bottom - m_geometryBounds.top;

// Calculate a few factors for converting 2D to 3D
float radius = logicalSize.Width / 2 - 50;
float circumference = 2 * 3.14159f * radius;
float scale = circumference / geometryWidth;
float height = scale * geometryHeight;

for (size_t polygonIndex = 0; polygonIndex < m_srcPolygons.size(); polygonIndex++)
{
const Polygon& srcPolygon = m_srcPolygons.at(polygonIndex);
Polygon& dstPolygon = m_dstPolygons.at(polygonIndex);

for (size_t pointIndex = 0; pointIndex < srcPolygon.Points.size(); pointIndex++)
{
const D2D1_POINT_2F pt = srcPolygon.Points.at(pointIndex);
float radians = 2 * 3.14159f * (pt.x - m_geometryBounds.left) / geometryWidth;
float x = radius * sin(radians);
float z = radius * cos(radians);
float y = height * ((pt.y - m_geometryBounds.top) / geometryHeight - 0.5f);
dstPolygon.Points.at(pointIndex) = Point2F(x, y);
}
}

// Create path geometry from Polygon collection
DX::ThrowIfFailed(
Polygon::CreateGeometry(m_deviceResources->GetD2DFactory(),
m_dstPolygons,
&m_pathGeometry)
);
}

Figure 3 From 2D to 3D to 2D in the CircularText Program

Free download pdf