Direct2d has a number of different objects that you can use to compose an image. There are several canned shaped types, a generic shape type called a geometry, and several types of brushes for filling and drawing lines with. Shown below is a test board from my Direct2d engine in progress.
Other systems call them paths, but Direct2d calls them geometries. A geometry is a connected list of lines or curves that can be stored and then redrawn as needed. Geometries can have their lines strokes, or their interior filled. Geometries are manipulated in Direct2d using a pair of COM objects, an ID2D1PathGeometry and an ID2D1GeometrySink. I wrapped them into a simple class called a path.
class path {
public:
ID2D1PathGeometry *geometry;
ID2D1GeometrySink *sink;
path( direct2dContext *target ) : geometry( NULL ), sink( NULL )
{
HRESULT hr = target->d2DFactory->CreatePathGeometry( &geometry );
if (!SUCCEEDED(hr)) {
// UH, SOMETHING;
}
}
virtual ~path()
{
if (sink) sink->Release();
if (geometry) geometry->Release();
sink = NULL;
geometry = NULL;
}
void start_figure( D2D1_POINT_2F point )
{
if (geometry) {
geometry->Open( &sink );
if (sink)
sink->BeginFigure( point, D2D1_FIGURE_BEGIN_FILLED );
}
}
void add_line( D2D1_POINT_2F point )
{
if (sink) sink->AddLine( point );
}
void add_bezier( D2D1_POINT_2F point1, D2D1_POINT_2F point2, D2D1_POINT_2F point3 )
{
if (sink) sink->AddBezier( D2D1::BezierSegment( point1, point2, point3 ) );
}
void close_figure()
{
if (sink) {
sink->EndFigure( D2D1_FIGURE_END_CLOSED );
sink->Close();
sink->Release();
sink = NULL;
}
}
};
Internally, I create the geometry, as with all other objects, by passing around a Data Transfer Object (DTO). DTO's emerged to solve that nasty problem that arises when you have classes in two systems that have to talk to each other, but both classes have secrets or baggage such that you don't want to pass them directly. In my case, I don't know want consumers of my Direct2d wrapper classes to know what goes on inside of them, to maximize portability with an admitted performance hit. So I have dtos.
In this case, when I create my path, I fill it from a DTO as shown below. Note that normally a class derived from board would call this method during its load phase:
void directApplication::addPath( pathDto *_pathDto )
{
path *newPath = new path(this);
int count = 0;
std::list<pathPointDto>::iterator i;
for (i = _pathDto->points.begin(); i != _pathDto->points.end(); i++) {
D2D1_POINT_2F point = toPoint( i->point );
if (!count) {
newPath->start_figure( point );
} else {
newPath->add_line( point );
}
count++;
}
newPath->close_figure();
paths[ _pathDto->name ] = newPath;
}
In my design, I pass a pathInstance Dto to the draw path method. This gets used to select border and fill brushes, and a path. Note the use of the C++0x "auto" keyword. This says, the variable type is the type of the expression that fills it.
void directApplication::drawPath( pathInstance2dDto *_pathInstanceDto )
{
auto fill = brushes[ _pathInstanceDto->fillBrushName ];
auto border = brushes[ _pathInstanceDto->borderBrushName ];
auto p = paths[ _pathInstanceDto->pathName ];
Direct2d objects are positioned by adjusting the rendering transformation matrix on the renderTarget. Notice how D2D comes with a matrix class that uses statics to create various common transformation matrices, and, also supports matrix multiply.
D2D1::Matrix3x2F product = D2D1::Matrix3x2F::Rotation(_pathInstanceDto->rotation) * D2D1::Matrix3x2F::Translation(_pathInstanceDto->position.x, _pathInstanceDto->position.y ); renderTarget->SetTransform( product );
Finally, in my design, I unpack the Direct2D brushes and the path from my previous grabbed wrappers. Then, I call the Direct2d painting methods.
if (fill) {
renderTarget->FillGeometry( p->geometry, fill->getBrush() );
}
if (border && _pathInstanceDto->strokeWidth > 0.0) {
renderTarget->DrawGeometry( p->geometry, border->getBrush(), _pathInstanceDto->strokeWidth );
}
So in this article we explored geometries a little bit. Note that this implementation doesn't allow for the fancy stuff like bezier curves, but it can do filled shapes ok.
If you have Windows 7, you can download the EXE or source code. Note that Windows will howl about the files not being digitally signed. The source code requires Visual Studio 2010 C++. We'll talk about brushes in the next article.