static ComPtr<ID2D1PathGeometry1> vectorPathToID2D1PathGeometry(const QVectorPath &path, bool alias)
{
    ComPtr<ID2D1PathGeometry1> pathGeometry;
    HRESULT hr = factory()->CreatePathGeometry(pathGeometry.GetAddressOf());
    if (FAILED(hr)) {
        qWarning("%s: Could not create path geometry: %#x", __FUNCTION__, hr);
        return NULL;
    }

    if (path.isEmpty())
        return pathGeometry;

    ComPtr<ID2D1GeometrySink> sink;
    hr = pathGeometry->Open(sink.GetAddressOf());
    if (FAILED(hr)) {
        qWarning("%s: Could not create geometry sink: %#x", __FUNCTION__, hr);
        return NULL;
    }

    sink->SetFillMode(path.hasWindingFill() ? D2D1_FILL_MODE_WINDING
                                            : D2D1_FILL_MODE_ALTERNATE);

    bool inFigure = false;

    const QPainterPath::ElementType *types = path.elements();
    const int count = path.elementCount();
    const qreal *points = 0;

    QScopedArrayPointer<qreal> rounded_points;

    if (alias) {
        // Aliased painting, round to whole numbers
        rounded_points.reset(new qreal[count * 2]);
        points = rounded_points.data();

        for (int i = 0; i < (count * 2); i++)
            rounded_points[i] = qRound(path.points()[i]);
    } else {
        // Antialiased painting, keep original numbers
        points = path.points();
    }

    Q_ASSERT(points);

    if (types) {
        qreal x, y;

        for (int i = 0; i < count; i++) {
            x = points[i * 2];
            y = points[i * 2 + 1];

            switch (types[i]) {
            case QPainterPath::MoveToElement:
                if (inFigure)
                    sink->EndFigure(D2D1_FIGURE_END_OPEN);

                sink->BeginFigure(D2D1::Point2F(x, y), D2D1_FIGURE_BEGIN_FILLED);
                inFigure = true;
                break;

            case QPainterPath::LineToElement:
                sink->AddLine(D2D1::Point2F(x, y));
                break;

            case QPainterPath::CurveToElement:
            {
                Q_ASSERT((i + 2) < count);
                Q_ASSERT(types[i+1] == QPainterPath::CurveToDataElement);
                Q_ASSERT(types[i+2] == QPainterPath::CurveToDataElement);

                i++;
                const qreal x2 = points[i * 2];
                const qreal y2 = points[i * 2 + 1];

                i++;
                const qreal x3 = points[i * 2];
                const qreal y3 = points[i * 2 + 1];

                D2D1_BEZIER_SEGMENT segment = {
                    D2D1::Point2F(x, y),
                    D2D1::Point2F(x2, y2),
                    D2D1::Point2F(x3, y3)
                };

                sink->AddBezier(segment);
            }
                break;

            case QPainterPath::CurveToDataElement:
                qWarning("%s: Unhandled Curve Data Element", __FUNCTION__);
                break;
            }
        }
    } else {
        sink->BeginFigure(D2D1::Point2F(points[0], points[1]), D2D1_FIGURE_BEGIN_FILLED);
        inFigure = true;

        for (int i = 1; i < count; i++)
            sink->AddLine(D2D1::Point2F(points[i * 2], points[i * 2 + 1]));
    }

    if (inFigure) {
        if (path.hasImplicitClose())
            sink->AddLine(D2D1::Point2F(points[0], points[1]));

        sink->EndFigure(D2D1_FIGURE_END_OPEN);
    }

    sink->Close();

    return pathGeometry;
}
static ComPtr<ID2D1PathGeometry1> painterPathToPathGeometry(const QPainterPath &path)
{
    ComPtr<ID2D1PathGeometry1> geometry;
    ComPtr<ID2D1GeometrySink> sink;

    HRESULT hr = factory()->CreatePathGeometry(&geometry);
    if (FAILED(hr)) {
        qWarning("%s: Could not create path geometry: %#x", __FUNCTION__, hr);
        return NULL;
    }

    hr = geometry->Open(&sink);
    if (FAILED(hr)) {
        qWarning("%s: Could not create geometry sink: %#x", __FUNCTION__, hr);
        return NULL;
    }

    switch (path.fillRule()) {
    case Qt::WindingFill:
        sink->SetFillMode(D2D1_FILL_MODE_WINDING);
        break;
    case Qt::OddEvenFill:
        sink->SetFillMode(D2D1_FILL_MODE_ALTERNATE);
        break;
    }

    bool inFigure = false;

    for (int i = 0; i < path.elementCount(); i++) {
        const QPainterPath::Element element = path.elementAt(i);

        switch (element.type) {
        case QPainterPath::MoveToElement:
            if (inFigure)
                sink->EndFigure(D2D1_FIGURE_END_OPEN);

            sink->BeginFigure(to_d2d_point_2f(element), D2D1_FIGURE_BEGIN_FILLED);
            inFigure = true;
            break;

        case QPainterPath::LineToElement:
            sink->AddLine(to_d2d_point_2f(element));
            break;

        case QPainterPath::CurveToElement:
        {
            const QPainterPath::Element data1 = path.elementAt(++i);
            const QPainterPath::Element data2 = path.elementAt(++i);

            Q_ASSERT(i < path.elementCount());

            Q_ASSERT(data1.type == QPainterPath::CurveToDataElement);
            Q_ASSERT(data2.type == QPainterPath::CurveToDataElement);

            D2D1_BEZIER_SEGMENT segment;

            segment.point1 = to_d2d_point_2f(element);
            segment.point2 = to_d2d_point_2f(data1);
            segment.point3 = to_d2d_point_2f(data2);

            sink->AddBezier(segment);
        }
            break;

        case QPainterPath::CurveToDataElement:
            qWarning("%s: Unhandled Curve Data Element", __FUNCTION__);
            break;
        }
    }

    if (inFigure)
        sink->EndFigure(D2D1_FIGURE_END_OPEN);

    sink->Close();

    return geometry;
}
void JigsawPuzzleRenderer::CreatePuzzlePieceGeometry()
{
    // Create the geometry outline for the jigsaw puzzle piece.
    ComPtr<ID2D1PathGeometry> path;
    ComPtr<ID2D1GeometrySink> sink;

    DX::ThrowIfFailed(m_deviceResources->GetD2DFactory()->CreatePathGeometry(&path));
    DX::ThrowIfFailed(path->Open(&sink));

    // The following math calculates the information needed to determine where the arcs for the puzzle piece connector begin and end.
    // The inner radius is the radius of the arc that defines the stem of the connector. The outer radius is the radius of the arc
    // that defines the connector.

    float edgeLength = Constants::PuzzlePieceSize;
    float innerRadius = Constants::PuzzlePieceConnectorInnerRadius;
    float outerRadius = Constants::PuzzlePieceConnectorOuterRadius;
    float stemPosition = (edgeLength / 2.0f) - (innerRadius * 2.0f);
    float connectorParallelOffset = (2.0f * pow(innerRadius, 2.0f)) / (innerRadius + outerRadius);
    float connectorPerpendicularOffset = sqrt(pow(innerRadius, 2.0f) - pow(connectorParallelOffset, 2.0f)) + innerRadius;

    sink->BeginFigure(D2D1::Point2F(0, 0), D2D1_FIGURE_BEGIN_FILLED);
    sink->AddLine(D2D1::Point2F(edgeLength, 0));
    sink->AddLine(D2D1::Point2F(edgeLength, stemPosition));
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(edgeLength - connectorPerpendicularOffset, stemPosition + connectorParallelOffset),
            D2D1::SizeF(innerRadius, innerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_CLOCKWISE,
            D2D1_ARC_SIZE_SMALL
            )
        );
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(edgeLength - connectorPerpendicularOffset, edgeLength - stemPosition - connectorParallelOffset),
            D2D1::SizeF(outerRadius, outerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
            D2D1_ARC_SIZE_LARGE
            )
        );
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(edgeLength, edgeLength - stemPosition),
            D2D1::SizeF(innerRadius, innerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_CLOCKWISE,
            D2D1_ARC_SIZE_SMALL
            )
        );
    sink->AddLine(D2D1::Point2F(edgeLength, edgeLength));
    sink->AddLine(D2D1::Point2F(edgeLength - stemPosition, edgeLength));
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(edgeLength - stemPosition - connectorParallelOffset, edgeLength + connectorPerpendicularOffset),
            D2D1::SizeF(innerRadius, innerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
            D2D1_ARC_SIZE_SMALL
            )
        );
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(stemPosition + connectorParallelOffset, edgeLength + connectorPerpendicularOffset),
            D2D1::SizeF(outerRadius, outerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_CLOCKWISE,
            D2D1_ARC_SIZE_LARGE
            )
        );
    sink->AddArc(
        D2D1::ArcSegment(
            D2D1::Point2F(stemPosition, edgeLength),
            D2D1::SizeF(innerRadius, innerRadius),
            0.0f,
            D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
            D2D1_ARC_SIZE_SMALL
            )
        );
    sink->AddLine(D2D1::Point2F(0, edgeLength));
    sink->EndFigure(D2D1_FIGURE_END_CLOSED);

    DX::ThrowIfFailed(sink->Close());
    m_geometry = path;
}