I've been wanting to find a better way to get 2D geometry content into my XNA Game Studio projects. Laying out lines and triangles on graph paper, then entering coordinates by hand into code files gets old fast! I was looking for a 2D illustration package that I could use instead, then translate from its data files into something I could read from XNA.
Microsoft Expression Design is a pretty nice tool for laying out lines and shapes, and I was interested to see that it can export its documents as WPF XAML, which looked fairly straightforward to parse. I decided to see if I could use the XNA Content Pipeline to translate the XAML into lines and triangles that I could render.
XAML is based on XML, so I thought at first that I would parse the XML nodes directly with something like XmlReader. Then I realized that it was pretty easy to let Windows Presentation Foundation take care of the parsing, and just traverse the resulting WPF object hierarchy to get the path information. The XAML files I generated in Expression Design had a top-level Canvas object, which contained Canvas child objects for each layer. These inner Canvas objects had various Shapes -- the ones I was interested in were Paths. A method called GetFlattenedPathGeometry took care of converting any curves into line segments -- then I just had to iterate through the line segments and record the coordinates.
This was my first time attempting to extend the XNA Content Pipeline. It took a bit of background reading to get the hang of it, but it seems straightforward to me now that I understand the concepts. My Content Importer takes a XAML file as input and generates a "MyDataTypes.MyShape" object as output. I didn't need a Content Processor, since my app works with MyShape objects (there was no intermediate type needed). And thanks to XNA Game Studio 3.1's automatic XNB serialization, I didn't have to write a ContentTypeWriter or ContentTypeReader for MyShape.
One minor thing I had to deal with was a coordinate system change. Expression Designer and WPF treat the top-left corner of the document as the origin, with Y increasing as you move down the screen. I am more used to Y increasing as you go up the screen, so all my content was appearing upside-down at first (though I could modify my projection matrix to match WPF as one way to address this). I decided to have my Content Importer perform the Y inversion with a "Y = CanvasHeight - Y" adjustment.
Getting the line segments for a path was fairly easy. Getting the triangles for the interior of the path required a bit more work. Basically I needed a way to tessellate arbitrary convex polygons into triangles (this is sometimes called triangulation, since the term tessellation can mean other things). Algorithms exist to do triangulation, but I was having trouble finding some pre-existing code to use. Then I learned that Direct2D has the ability to tessellate geometry, and decided to try using that.
Direct2D is fairly new, and is built into Windows 7. It can be installed on Windows Vista SP2 if you've installed the "Platform Update for Windows Vista" -- see KB 971644. If you're still using Windows XP, I'm afraid you're out of luck -- you'll have to find another way to triangulate shapes. Headers/libs for Direct2D are available both in the latest Windows SDK and the August 2009 DirectX SDK.
I wrote a standalone tool called D2DTessellate.exe to handle this triangulation task while importing content. It's written in C++, not C#, so I've provided a executable as well as the source code, in case you don't have Visual C++ or the necessary headers/libs installed. The tool reads a text file containing a list of points specifying a polygon, calls into Direct2D to tessellate it, then writes the resulting triangles to another text file. It's short and it works! My content importer invokes D2DTessellate.exe as needed for each polygon.
Note that the Content Importer needs to use WPF and D2D, but everything is just "MyShape" by the time the game loads the content from the Content Pipeline. So games that use XAML-based content with this method can still run on the Xbox -- the game itself doesn't have any direct dependencies on those Windows-only technologies.
Back in my XNA Game Studio game app, the main new thing I needed to add was a SimpleMesh class for drawing these lists of triangles associated with MyShape. I used non-indexed triangle lists, since that matched the data coming out of D2DTessellate. Since a lot of vertices are shared by multiple triangles, it would be possible to save a little space and improve perf by using index buffers and re-using vertex data instead of replicating it. My shapes were small enough that it didn't really matter. I created a spiffy zig-zag pixel shader as an example of a more interesting way to render the mesh triangles than a solid color. I also added a wireframe option to the test app, so you can see the tessellation.
I'm really happy with the result -- now I can draw shapes in Expression Designer, export the XAML, and easily import and draw it in my XNA Game Studio apps. Rock on!