diff --git a/src/map/renderer/painterrenderer.cpp b/src/map/renderer/painterrenderer.cpp index 94da216..ddc9137 100644 --- a/src/map/renderer/painterrenderer.cpp +++ b/src/map/renderer/painterrenderer.cpp @@ -1,215 +1,215 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "painterrenderer.h" #include "view.h" #include "../scene/scenegraph.h" #include #include #include #include #include using namespace KOSMIndoorMap; PainterRenderer::PainterRenderer() = default; PainterRenderer::~PainterRenderer() = default; void PainterRenderer::setPaintDevice(QPaintDevice *device) { m_device = device; } void PainterRenderer::render(const SceneGraph &sg, View *view) { QElapsedTimer frameTimer; frameTimer.start(); m_view = view; beginRender(); - renderBackground(sg.m_bgColor); + renderBackground(sg.backgroundColor()); - for (const auto &layerOffsets : sg.m_layerOffsets) { - const auto layerBegin = sg.m_items.begin() + layerOffsets.first; - const auto layerEnd = sg.m_items.begin() + layerOffsets.second; + for (const auto &layerOffsets : sg.layerOffsets()) { + const auto layerBegin = sg.itemsBegin(layerOffsets); + const auto layerEnd = sg.itemsEnd(layerOffsets); //qDebug() << "rendering layer" << (*layerBegin)->layer; // select elements currently in view m_renderBatch.clear(); m_renderBatch.reserve(layerOffsets.second - layerOffsets.first); const QRectF screenRect(QPointF(0, 0), QSizeF(m_view->screenWidth(), m_view->screenHeight())); for (auto it = layerBegin; it != layerEnd; ++it) { if ((*it)->inSceneSpace() && m_view->viewport().intersects((*it)->boundingRect())) { m_renderBatch.push_back((*it).get()); } if ((*it)->inHUDSpace()) { auto bbox = (*it)->boundingRect(); bbox.moveCenter(m_view->mapSceneToScreen(bbox.center())); if (screenRect.intersects(bbox)) { m_renderBatch.push_back((*it).get()); } } } for (auto phase : {SceneGraphItem::FillPhase, SceneGraphItem::CasingPhase, SceneGraphItem::StrokePhase, SceneGraphItem::LabelPhase}) { beginPhase(phase); for (const auto item : m_renderBatch) { if ((item->renderPhases() & phase) == 0) { continue; } if (auto i = dynamic_cast(item)) { renderPolygon(i, phase); } else if (auto i = dynamic_cast(item)) { renderMultiPolygon(i, phase); } else if (auto i = dynamic_cast(item)) { renderPolyline(i, phase); } else if (auto i = dynamic_cast(item)) { renderLabel(i); } else { qCritical() << "Unsupported scene graph item!"; } } } } endRender(); m_view = nullptr; - qDebug() << "rendering took:" << frameTimer.elapsed() << "ms for" << sg.m_items.size() << "items on" << sg.m_layerOffsets.size() << "layers"; + qDebug() << "rendering took:" << frameTimer.elapsed() << "ms for" << sg.itemCount() << "items on" << sg.layerOffsets().size() << "layers"; } void PainterRenderer::beginRender() { m_painter.begin(m_device); } void PainterRenderer::renderBackground(const QColor &bgColor) { m_painter.fillRect(0, 0, m_view->screenWidth(), m_view->screenHeight(), bgColor); } void PainterRenderer::beginPhase(SceneGraphItem::RenderPhase phase) { switch (phase) { case SceneGraphItem::NoPhase: Q_UNREACHABLE(); case SceneGraphItem::FillPhase: m_painter.setPen(Qt::NoPen); m_painter.setTransform(m_view->sceneToScreenTransform()); m_painter.setClipRect(m_view->viewport()); m_painter.setRenderHint(QPainter::Antialiasing, false); break; case SceneGraphItem::CasingPhase: case SceneGraphItem::StrokePhase: m_painter.setBrush(Qt::NoBrush); m_painter.setTransform(m_view->sceneToScreenTransform()); m_painter.setClipRect(m_view->viewport()); m_painter.setRenderHint(QPainter::Antialiasing, true); break; case SceneGraphItem::LabelPhase: m_painter.setTransform({}); m_painter.setRenderHint(QPainter::Antialiasing, true); break; } } void PainterRenderer::renderPolygon(PolygonItem *item, SceneGraphItem::RenderPhase phase) { if (phase == SceneGraphItem::FillPhase) { m_painter.setBrush(item->brush); m_painter.drawPolygon(item->polygon); } else { auto p = item->pen; p.setWidthF(m_view->mapScreenDistanceToSceneDistance(item->pen.widthF())); m_painter.setPen(p); m_painter.drawPolygon(item->polygon); } } void PainterRenderer::renderMultiPolygon(MultiPolygonItm *item, SceneGraphItem::RenderPhase phase) { if (phase == SceneGraphItem::FillPhase) { m_painter.setBrush(item->brush); m_painter.drawPath(item->path); } else { auto p = item->pen; p.setWidthF(m_view->mapScreenDistanceToSceneDistance(item->pen.widthF())); m_painter.setPen(p); m_painter.drawPath(item->path); } } void PainterRenderer::renderPolyline(PolylineItem *item, SceneGraphItem::RenderPhase phase) { if (phase == SceneGraphItem::StrokePhase) { auto p = item->pen; p.setWidthF(m_view->mapMetersToScene(item->pen.widthF())); m_painter.setPen(p); m_painter.drawPolyline(item->path); } else { auto p = item->casingPen; p.setWidthF(m_view->mapMetersToScene(item->pen.widthF()) + m_view->mapScreenDistanceToSceneDistance(item->casingPen.widthF())); m_painter.setPen(p); m_painter.drawPolyline(item->path); } } void PainterRenderer::renderLabel(LabelItem *item) { if (!item->hasFineBbox) { QFontMetricsF fm(item->font); item->bbox = fm.boundingRect(item->text); item->bbox.moveCenter(item->pos); item->hasFineBbox = true; } m_painter.save(); m_painter.translate(m_view->mapSceneToScreen(item->pos)); m_painter.rotate(item->angle); auto box = item->bbox; box.moveCenter({0.0, 0.0}); // draw shield // @see https://wiki.openstreetmap.org/wiki/MapCSS/0.2#Shield_properties auto w = item->casingWidth + item->frameWidth + 2.0; if (item->casingWidth > 0.0 && item->casingColor.alpha() > 0) { m_painter.fillRect(box.adjusted(-w, -w, w, w), item->casingColor); } w -= item->casingWidth; if (item->frameWidth > 0.0 && item->frameColor.alpha() > 0) { m_painter.fillRect(box.adjusted(-w, -w, w, w), item->frameColor); } w -= item->frameWidth; if (item->shieldColor.alpha() > 0) { m_painter.fillRect(box.adjusted(-w, -w, w, w), item->shieldColor); } // draw text m_painter.setPen(item->color); m_painter.setFont(item->font); m_painter.drawText(box.bottomLeft() - QPointF(0, QFontMetricsF(item->font).descent()), item->text); m_painter.restore(); } void PainterRenderer::endRender() { m_painter.end(); } diff --git a/src/map/scene/scenegraph.cpp b/src/map/scene/scenegraph.cpp index e807646..b5cb5ad 100644 --- a/src/map/scene/scenegraph.cpp +++ b/src/map/scene/scenegraph.cpp @@ -1,100 +1,125 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "scenegraph.h" #include "scenegraphitem.h" #include #include #include using namespace KOSMIndoorMap; SceneGraph::SceneGraph() = default; SceneGraph::SceneGraph(SceneGraph&&) = default; SceneGraph::~SceneGraph() = default; SceneGraph& SceneGraph::operator=(SceneGraph &&other) = default; void SceneGraph::addItem(SceneGraphItem *item) { m_items.push_back(std::unique_ptr(item)); } void SceneGraph::zSort() { /* The MapCSS spec says we have to render in the following order: * - Objects with lower layer should always be rendered first. * - Within a layer, first all fills are rendered, then all casings, then all strokes, then all icons and labels. * - Within each of those categories, objects are ordered according to z-index. * - If all of the above are equal, the order is undefined. */ std::stable_sort(m_items.begin(), m_items.end(), [](const auto &lhs, const auto &rhs) { if (lhs->level == rhs->level) { if (lhs->layer == rhs->layer) { return lhs->z < rhs->z; } return lhs->layer < rhs->layer; } return lhs->level < rhs->level; }); recomputeLayerIndex(); } void SceneGraph::clear() { m_items.clear(); m_layerOffsets.clear(); } +QColor SceneGraph::backgroundColor() const +{ + return m_bgColor; +} + void SceneGraph::setBackgroundColor(const QColor &bg) { m_bgColor = bg; } void SceneGraph::recomputeLayerIndex() { m_layerOffsets.clear(); if (m_items.empty()) { return; } auto prevLayer = m_items.front()->layer; auto prevIndex = 0; for (auto it = m_items.begin(); it != m_items.end();) { it = std::upper_bound(it, m_items.end(), prevLayer, [](auto lhs, const auto &rhs) { return lhs < rhs->layer; }); const auto nextIndex = std::distance(m_items.begin(), it); m_layerOffsets.push_back(std::make_pair(prevIndex, nextIndex)); prevIndex = nextIndex; if (it != m_items.end()) { prevLayer = (*it)->layer; } } } void SceneGraph::itemsAt(QPointF pos) { // ### temporary for testing for (const auto &item : m_items) { if (item->inSceneSpace() && item->boundingRect().contains(pos)) { qDebug() << item->element.url() << item->element.tagValue("name"); } // TODO HUD space elements } } + +const std::vector& SceneGraph::layerOffsets() const +{ + return m_layerOffsets; +} + +SceneGraph::SceneGraphItemIter SceneGraph::itemsBegin(SceneGraph::LayerOffset layer) const +{ + return m_items.begin() + layer.first; +} + +SceneGraph::SceneGraphItemIter SceneGraph::itemsEnd(SceneGraph::LayerOffset layer) const +{ + return m_items.begin() + layer.second; +} + +std::size_t SceneGraph::itemCount() const +{ + return m_items.size(); +} diff --git a/src/map/scene/scenegraph.h b/src/map/scene/scenegraph.h index 16f444f..741fe43 100644 --- a/src/map/scene/scenegraph.h +++ b/src/map/scene/scenegraph.h @@ -1,67 +1,76 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KOSMINDOORMAP_SCENEGRAPH_H #define KOSMINDOORMAP_SCENEGRAPH_H #include #include #include class QPointF; namespace KOSMIndoorMap { class SceneGraphItem; /** Scene graph of the currently displayed level. */ class SceneGraph { public: explicit SceneGraph(); SceneGraph(const SceneGraph&) = delete; SceneGraph(SceneGraph&&); ~SceneGraph(); SceneGraph& operator=(const SceneGraph&) = delete; SceneGraph& operator=(SceneGraph &&other); void addItem(SceneGraphItem *item); void zSort(); void clear(); /** Canvas background color. */ + QColor backgroundColor() const; void setBackgroundColor(const QColor &bg); /** Items at scene coordinate @p pos. * TODO this still needs a lot of work to be useful, mostly for debugging atm. */ void itemsAt(QPointF pos); + // renderer interface + typedef std::pair LayerOffset; + const std::vector& layerOffsets() const; + + typedef std::vector>::const_iterator SceneGraphItemIter; + SceneGraphItemIter itemsBegin(LayerOffset layer) const; + SceneGraphItemIter itemsEnd(LayerOffset layer) const; + std::size_t itemCount() const; + private: void recomputeLayerIndex(); - friend class PainterRenderer; // TODO std::vector> m_items; std::vector> m_layerOffsets; QColor m_bgColor; }; } #endif // KOSMINDOORMAP_SCENEGRAPH_H