diff --git a/src/map/renderer/painterrenderer.cpp b/src/map/renderer/painterrenderer.cpp index ddc9137..d6ed3d2 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.backgroundColor()); 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)) { + } 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.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) +void PainterRenderer::renderMultiPolygon(MultiPolygonItem *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/renderer/painterrenderer.h b/src/map/renderer/painterrenderer.h index 8629c93..6ad0907 100644 --- a/src/map/renderer/painterrenderer.h +++ b/src/map/renderer/painterrenderer.h @@ -1,68 +1,68 @@ /* 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_PAINTERRENDERER_H #define KOSMINDOORMAP_PAINTERRENDERER_H #include "../scene/scenegraphitem.h" #include #include class QPaintDevice; namespace KOSMIndoorMap { class LabelItem; class PolygonItem; class PolylineItem; class SceneGraph; class View; /** QPainter-based renderer of a SceneGraph. * Trying to keep this somewhat backend-agnostic to possibly implement a 3D renderer in the future. */ class PainterRenderer { public: explicit PainterRenderer(); ~PainterRenderer(); void setPaintDevice(QPaintDevice *device); void render(const SceneGraph &sg, View *view); private: void beginRender(); void beginPhase(SceneGraphItem::RenderPhase phase); void renderBackground(const QColor &bgColor); void renderPolygon(PolygonItem *item, SceneGraphItem::RenderPhase phase); - void renderMultiPolygon(MultiPolygonItm *item, SceneGraphItem::RenderPhase phase); + void renderMultiPolygon(MultiPolygonItem *item, SceneGraphItem::RenderPhase phase); void renderPolyline(PolylineItem *item, SceneGraphItem::RenderPhase phase); void renderLabel(LabelItem *item); void endRender(); QPaintDevice *m_device = nullptr; QPainter m_painter; View *m_view = nullptr; std::vector m_renderBatch; // member rather than function-local to preserve allocations }; } #endif // KOSMINDOORMAP_PAINTERRENDERER_H diff --git a/src/map/scene/scenecontroller.cpp b/src/map/scene/scenecontroller.cpp index 4094938..370412b 100644 --- a/src/map/scene/scenecontroller.cpp +++ b/src/map/scene/scenecontroller.cpp @@ -1,449 +1,450 @@ /* 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 "scenecontroller.h" #include "scenegeometry.h" #include "scenegraph.h" #include "scenegraphitem.h" #include "../loader/mapdata.h" #include "../renderer/view.h" #include "../style/mapcssdeclaration.h" #include "../style/mapcssstyle.h" #include "../style/mapcssstate.h" #include #include #include #include #include #include using namespace KOSMIndoorMap; SceneController::SceneController() = default; SceneController::~SceneController() = default; void SceneController::setDataSet(const MapData *data) { m_data = data; } void SceneController::setStyleSheet(const MapCSSStyle *styleSheet) { m_styleSheet = styleSheet; } void SceneController::setView(const View *view) { m_view = view; } void SceneController::updateScene(SceneGraph &sg) const { QElapsedTimer sgUpdateTimer; sgUpdateTimer.start(); - sg.clear(); // TODO reuse what is still valid + sg.beginSwap(); updateCanvas(sg); // find all intermediate levels below or above the currently selected "full" level auto it = m_data->m_levelMap.find(MapLevel(m_view->level())); if (it == m_data->m_levelMap.end()) { return; } auto beginIt = it; if (beginIt != m_data->m_levelMap.begin()) { do { --beginIt; } while (!(*beginIt).first.isFullLevel() && beginIt != m_data->m_levelMap.begin()); ++beginIt; } auto endIt = it; for (++endIt; endIt != m_data->m_levelMap.end(); ++endIt) { if ((*endIt).first.isFullLevel()) { break; } } // for each level, update or create scene graph elements for (auto it = beginIt; it != endIt; ++it) { for (auto e : (*it).second) { updateElement(e, (*it).first.numericLevel(), sg); } } sg.zSort(); + sg.endSwap(); qDebug() << "updated scenegraph took" << sgUpdateTimer.elapsed() << "ms"; } void SceneController::updateCanvas(SceneGraph &sg) const { sg.setBackgroundColor(QGuiApplication::palette().color(QPalette::Base)); m_defaultTextColor = QGuiApplication::palette().color(QPalette::Text); m_defaultFont = QGuiApplication::font(); m_styleSheet->evaluateCanvas(m_styleResult); for (auto decl : m_styleResult.declarations()) { switch (decl->property()) { case MapCSSDeclaration::FillColor: sg.setBackgroundColor(decl->colorValue()); break; case MapCSSDeclaration::TextColor: m_defaultTextColor = decl->colorValue(); break; default: break; } } } void SceneController::updateElement(OSM::Element e, int level, SceneGraph &sg) const { MapCSSState state; state.element = e; state.zoomLevel = m_view->zoomLevel(); m_styleSheet->evaluate(state, m_styleResult); if (m_styleResult.hasAreaProperties()) { - PolygonBaseItem *item; + std::unique_ptr item; if (e.type() == OSM::Type::Relation && e.tagValue("type") == QLatin1String("multipolygon")) { - auto i = new MultiPolygonItm; + auto i = new MultiPolygonItem; i->path = createPath(e, m_labelPlacementPath); - item = i; + item.reset(i); } else { auto i = new PolygonItem; i->polygon = createPolygon(e); m_labelPlacementPath = i->polygon; - item = i; + item.reset(i); } double lineOpacity = 1.0; double fillOpacity = 1.0; initializePen(item->pen); for (auto decl : m_styleResult.declarations()) { - applyGenericStyle(decl, item); + applyGenericStyle(decl, item.get()); applyPenStyle(decl, item->pen, lineOpacity); switch (decl->property()) { case MapCSSDeclaration::FillColor: item->brush.setColor(decl->colorValue()); item->brush.setStyle(Qt::SolidPattern); break; case MapCSSDeclaration::FillOpacity: fillOpacity = decl->doubleValue(); break; default: break; } } finalizePen(item->pen, lineOpacity); if (item->brush.style() == Qt::SolidPattern && fillOpacity < 1.0) { auto c = item->brush.color(); c.setAlphaF(c.alphaF() * fillOpacity); item->brush.setColor(c); } - addItem(sg, e, level, item); + addItem(sg, e, level, std::move(item)); } else if (m_styleResult.hasLineProperties()) { - auto item = new PolylineItem; + auto item = std::make_unique(); item->path = createPolygon(e); double lineOpacity = 1.0; double casingOpacity = 1.0; initializePen(item->pen); initializePen(item->casingPen); for (auto decl : m_styleResult.declarations()) { - applyGenericStyle(decl, item); + applyGenericStyle(decl, item.get()); applyPenStyle(decl, item->pen, lineOpacity); applyCasingPenStyle(decl, item->casingPen, casingOpacity); } finalizePen(item->pen, lineOpacity); finalizePen(item->casingPen, casingOpacity); m_labelPlacementPath = item->path; - addItem(sg, e, level, item); + addItem(sg, e, level, std::move(item)); } if (m_styleResult.hasLabelProperties()) { QString text; auto textDecl = m_styleResult.declaration(MapCSSDeclaration::Text); if (!textDecl) { textDecl = m_styleResult.declaration(MapCSSDeclaration::ShieldText); } if (textDecl) { if (!textDecl->keyValue().isEmpty()) { text = e.tagValue(textDecl->keyValue().constData()); } else { text = textDecl->stringValue(); } } if (!text.isEmpty()) { - auto item = new LabelItem; + auto item = std::make_unique(); item->text = text; item->font = m_defaultFont; item->color = m_defaultTextColor; if (m_styleResult.hasAreaProperties()) { item->pos = SceneGeometry::polygonCentroid(m_labelPlacementPath); } else if (m_styleResult.hasLineProperties()) { // TODO compute placement at half distance along the path } if (item->pos.isNull()) { item->pos = m_view->mapGeoToScene(e.center()); // node or something failed above } double textOpacity = 1.0; double shieldOpacity = 1.0; for (auto decl : m_styleResult.declarations()) { - applyGenericStyle(decl, item); + applyGenericStyle(decl, item.get()); applyFontStyle(decl, item->font); switch (decl->property()) { case MapCSSDeclaration::TextColor: item->color = decl->colorValue(); break; case MapCSSDeclaration::TextOpacity: textOpacity = decl->doubleValue(); break; case MapCSSDeclaration::ShieldCasingColor: item->casingColor = decl->colorValue(); break; case MapCSSDeclaration::ShieldCasingWidth: item->casingWidth = decl->doubleValue(); break; case MapCSSDeclaration::ShieldColor: item->shieldColor = decl->colorValue(); break; case MapCSSDeclaration::ShieldOpacity: shieldOpacity = decl->doubleValue(); break; case MapCSSDeclaration::ShieldFrameColor: item->frameColor = decl->colorValue(); break; case MapCSSDeclaration::ShieldFrameWidth: item->frameWidth = decl->doubleValue(); break; case MapCSSDeclaration::TextPosition: if (decl->textFollowsLine() && m_labelPlacementPath.size() > 1) { item->angle = SceneGeometry::angleForPath(m_labelPlacementPath); } break; default: break; } } if (item->color.isValid() && textOpacity < 1.0) { auto c = item->color; c.setAlphaF(c.alphaF() * textOpacity); item->color = c; } if (item->shieldColor.isValid() && shieldOpacity < 1.0) { auto c = item->shieldColor; c.setAlphaF(c.alphaF() * shieldOpacity); item->shieldColor = c; } - addItem(sg, e, level, item); + addItem(sg, e, level, std::move(item)); } } } QPolygonF SceneController::createPolygon(OSM::Element e) const { const auto path = e.outerPath(m_data->dataSet()); QPolygonF poly; poly.reserve(path.size()); for (auto node : path) { poly.push_back(m_view->mapGeoToScene(node->coordinate)); } return poly; } // @see https://wiki.openstreetmap.org/wiki/Relation:multipolygon QPainterPath SceneController::createPath(const OSM::Element e, QPolygonF &outerPath) const { assert(e.type() == OSM::Type::Relation); outerPath = createPolygon(e); QPainterPath path; path.addPolygon(outerPath); // assemble the outer polygon, which can be represented as a set of unsorted lines here even for (const auto &mem : e.relation()->members) { const bool isInner = mem.role == QLatin1String("inner"); if (mem.type != OSM::Type::Way || !isInner) { continue; } auto wayIt = std::lower_bound(m_data->dataSet().ways.begin(), m_data->dataSet().ways.end(), mem.id); if (wayIt == m_data->dataSet().ways.end() || (*wayIt).id != mem.id) { continue; } const auto subPoly = createPolygon(OSM::Element(&(*wayIt))); QPainterPath subPath; subPath.addPolygon(subPoly); path = path.subtracted(subPath); } return path; } void SceneController::applyGenericStyle(const MapCSSDeclaration *decl, SceneGraphItem *item) const { if (decl->property() == MapCSSDeclaration::ZIndex) { item->z = decl->intValue(); } } void SceneController::applyPenStyle(const MapCSSDeclaration *decl, QPen &pen, double &opacity) const { switch (decl->property()) { case MapCSSDeclaration::Color: pen.setColor(decl->colorValue()); break; case MapCSSDeclaration::Width: pen.setWidthF(decl->doubleValue()); break; case MapCSSDeclaration::Dashes: pen.setDashPattern(decl->dashesValue()); break; case MapCSSDeclaration::LineCap: pen.setCapStyle(decl->capStyle()); break; case MapCSSDeclaration::LineJoin: pen.setJoinStyle(decl->joinStyle()); break; case MapCSSDeclaration::Opacity: opacity = decl->doubleValue(); break; default: break; } } void SceneController::applyCasingPenStyle(const MapCSSDeclaration *decl, QPen &pen, double &opacity) const { switch (decl->property()) { case MapCSSDeclaration::CasingColor: pen.setColor(decl->colorValue()); break; case MapCSSDeclaration::CasingWidth: pen.setWidthF(decl->doubleValue()); break; case MapCSSDeclaration::CasingDashes: pen.setDashPattern(decl->dashesValue()); break; case MapCSSDeclaration::CasingLineCap: pen.setCapStyle(decl->capStyle()); break; case MapCSSDeclaration::CasingLineJoin: pen.setJoinStyle(decl->joinStyle()); break; case MapCSSDeclaration::CasingOpacity: opacity = decl->doubleValue(); break; default: break; } } void SceneController::applyFontStyle(const MapCSSDeclaration *decl, QFont &font) const { switch (decl->property()) { case MapCSSDeclaration::FontFamily: font.setFamily(decl->stringValue()); break; case MapCSSDeclaration::FontSize: font.setPointSizeF(decl->doubleValue()); // TODO unit support break; case MapCSSDeclaration::FontWeight: font.setBold(decl->isBoldStyle()); break; case MapCSSDeclaration::FontStyle: font.setItalic(decl->isItalicStyle()); break; case MapCSSDeclaration::FontVariant: font.setCapitalization(decl->capitalizationStyle()); break; case MapCSSDeclaration::TextDecoration: font.setUnderline(decl->isUnderlineStyle()); break; case MapCSSDeclaration::TextTransform: font.setCapitalization(decl->capitalizationStyle()); break; default: break; } } void SceneController::initializePen(QPen &pen) const { pen.setColor(Qt::transparent); // default according to spec pen.setCapStyle(Qt::FlatCap); pen.setJoinStyle(Qt::RoundJoin); pen.setStyle(Qt::SolidLine); } void SceneController::finalizePen(QPen &pen, double opacity) const { if (pen.color().isValid() && opacity < 1.0) { auto c = pen.color(); c.setAlphaF(c.alphaF() * opacity); pen.setColor(c); } if (pen.color().alphaF() == 0.0) { pen.setStyle(Qt::NoPen); // so the renderer can skip this entirely } } -void SceneController::addItem(SceneGraph &sg, OSM::Element e, int level, SceneGraphItem *item) const +void SceneController::addItem(SceneGraph &sg, OSM::Element e, int level, std::unique_ptr &&item) const { item->element = e; item->level = level; // get the OSM layer, if set const auto layerStr = e.tagValue(QLatin1String("layer")); if (!layerStr.isEmpty()) { bool success = false; const auto layer = layerStr.toInt(&success); if (success) { // ### Ignore layer information when it matches the level // This is very wrong according to the specification, however it looks that in many places // layer and level tags aren't correctly filled, possibly a side-effect of layer pre-dating // level and layers not having been properly updated when retrofitting level information // Strictly following the MapCSS rendering order yields sub-optimal results in that case, with // relevant elements being hidden. // // Ideally we find a way to detect the presence of that problem, and only then enabling this // workaround, but until we have this, this seems to produce better results in all tests. if (level != layer * 10) { item->layer = layer; } } else { qWarning() << "Invalid layer:" << e.url() << layerStr; } } - sg.addItem(item); + sg.addItem(std::move(item)); } diff --git a/src/map/scene/scenecontroller.h b/src/map/scene/scenecontroller.h index 6161ceb..e2b53e6 100644 --- a/src/map/scene/scenecontroller.h +++ b/src/map/scene/scenecontroller.h @@ -1,88 +1,88 @@ /* 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_SCENECONTROLLER_H #define KOSMINDOORMAP_SCENECONTROLLER_H #include #include "../style/mapcssresult.h" class QPolygonF; class QString; namespace OSM { class DataSet; class Element; class Node; class Relation; } namespace KOSMIndoorMap { class MapData; class MapCSSStyle; class SceneGraph; class SceneGraphItem; class View; /** Creates/updates the scene graph based on a given style sheet and view. */ class SceneController { public: explicit SceneController(); ~SceneController(); void setDataSet(const MapData *data); void setStyleSheet(const MapCSSStyle *styleSheet); void setView(const View *view); /** Creates or updates @p sg based on the currently set style and view settings. * When possible, provide the scene graph of the previous run to re-use scene graph elements that didn't change. */ void updateScene(SceneGraph &sg) const; private: void updateCanvas(SceneGraph &sg) const; void updateElement(OSM::Element e, int level, SceneGraph &sg) const; QPolygonF createPolygon(OSM::Element e) const; QPainterPath createPath(OSM::Element e, QPolygonF &outerPath) const; void applyGenericStyle(const MapCSSDeclaration *decl, SceneGraphItem *item) const; void applyPenStyle(const MapCSSDeclaration *decl, QPen &pen, double &opacity) const; void applyCasingPenStyle(const MapCSSDeclaration *decl, QPen &pen, double &opacity) const; void applyFontStyle(const MapCSSDeclaration *decl, QFont &font) const; void initializePen(QPen &pen) const; void finalizePen(QPen &pen, double opacity) const; - void addItem(SceneGraph &sg, OSM::Element e, int level, SceneGraphItem *item) const; + void addItem(SceneGraph &sg, OSM::Element e, int level, std::unique_ptr &&item) const; const MapData *m_data = nullptr; const MapCSSStyle *m_styleSheet = nullptr; const View *m_view = nullptr; mutable MapCSSResult m_styleResult; mutable QColor m_defaultTextColor; mutable QFont m_defaultFont; mutable QPolygonF m_labelPlacementPath; }; } #endif // KOSMINDOORMAP_SCENECONTROLLER_H diff --git a/src/map/scene/scenegraph.cpp b/src/map/scene/scenegraph.cpp index b5cb5ad..dc22a7b 100644 --- a/src/map/scene/scenegraph.cpp +++ b/src/map/scene/scenegraph.cpp @@ -1,125 +1,129 @@ /* 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) +void SceneGraph::addItem(std::unique_ptr &&item) { - m_items.push_back(std::unique_ptr(item)); + m_items.push_back(std::move(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() +void SceneGraph::beginSwap() { m_items.clear(); m_layerOffsets.clear(); } +void SceneGraph::endSwap() +{ +} + 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 741fe43..d3e5ce5 100644 --- a/src/map/scene/scenegraph.h +++ b/src/map/scene/scenegraph.h @@ -1,76 +1,78 @@ /* 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); + // scene builder interface + void beginSwap(); + void addItem(std::unique_ptr &&item); void zSort(); - void clear(); + void endSwap(); /** 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(); std::vector> m_items; std::vector> m_layerOffsets; QColor m_bgColor; }; } #endif // KOSMINDOORMAP_SCENEGRAPH_H diff --git a/src/map/scene/scenegraphitem.cpp b/src/map/scene/scenegraphitem.cpp index 0eb9682..a2d0a51 100644 --- a/src/map/scene/scenegraphitem.cpp +++ b/src/map/scene/scenegraphitem.cpp @@ -1,80 +1,80 @@ /* 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 "scenegraphitem.h" #include #include using namespace KOSMIndoorMap; SceneGraphItem::~SceneGraphItem() = default; bool SceneGraphItem::inSceneSpace() const { return renderPhases() & (FillPhase | StrokePhase | CasingPhase); } bool SceneGraphItem::inHUDSpace() const { return renderPhases() & LabelPhase; } uint8_t PolylineItem::renderPhases() const { return (pen.style() != Qt::NoPen ? StrokePhase : NoPhase) | (casingPen.style() != Qt::NoPen ? CasingPhase : NoPhase); } QRectF PolylineItem::boundingRect() const { return path.boundingRect(); // TODO do we need to cache this? } uint8_t PolygonBaseItem::renderPhases() const { return (pen.style() == Qt::NoPen ? NoPhase : StrokePhase) | (brush.style() == Qt::NoBrush ? NoPhase : FillPhase); } QRectF PolygonItem::boundingRect() const { return polygon.boundingRect(); // TODO do we need to cache this? } -QRectF MultiPolygonItm::boundingRect() const +QRectF MultiPolygonItem::boundingRect() const { return path.boundingRect(); // TODO do we need to cache this? } uint8_t LabelItem::renderPhases() const { return LabelPhase; } QRectF LabelItem::boundingRect() const { if (bbox.isValid()) { return bbox; } QFontMetricsF fm(font); bbox = QRectF(QPointF(0, 0), QPointF(fm.maxWidth() * text.size(), fm.lineSpacing() * text.size())); bbox.moveCenter(pos); return bbox; } diff --git a/src/map/scene/scenegraphitem.h b/src/map/scene/scenegraphitem.h index ba0c207..3a7c80e 100644 --- a/src/map/scene/scenegraphitem.h +++ b/src/map/scene/scenegraphitem.h @@ -1,140 +1,140 @@ /* 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_SCENEGRAPHITEM_H #define KOSMINDOORMAP_SCENEGRAPHITEM_H #include #include #include #include #include #include #include #include namespace KOSMIndoorMap { /** Base class for scene graph items. */ class SceneGraphItem { public: virtual ~SceneGraphItem(); /** See MapCSS spec: "Within a layer, first all fills are rendered, then all casings, then all strokes, then all icons and labels." .*/ enum RenderPhase : uint8_t { NoPhase = 0, FillPhase = 1, CasingPhase = 2, StrokePhase = 4, LabelPhase = 8, }; /** Returns in which phase this item needs to be rendered (can be multiple). */ virtual uint8_t renderPhases() const = 0; /** Bounding box of this item in scene coordinates. * Performance trumps precision here, so estimating this slightly larger rather than computing it expensively makes sense. */ virtual QRectF boundingRect() const = 0; /** Is this item drawn in scene coordinates (as oposed to HUD coordinates)? */ bool inSceneSpace() const; /** Is this item drawn in HUD coordinates (as oposed to scene coordinates)? */ bool inHUDSpace() const; /** The OSM::Element this item refers to. */ OSM::Element element; // TODO we probably don't need the full 32bit for those int level = 0; int layer = 0; int z = 0; }; /** A path/way/line item in the scenegraph. */ class PolylineItem : public SceneGraphItem { public: uint8_t renderPhases() const override; QRectF boundingRect() const override; QPolygonF path; QPen pen; QPen casingPen; }; /** Base item for filled polygons. */ class PolygonBaseItem : public SceneGraphItem { public: uint8_t renderPhases() const override; QBrush brush = Qt::NoBrush; QPen pen; }; /** A single filled polygon. */ class PolygonItem : public PolygonBaseItem { public: QRectF boundingRect() const override; QPolygonF polygon; }; /** Multi-polygon item, used for polygons with "holes" in them. */ -class MultiPolygonItm : public PolygonBaseItem +class MultiPolygonItem : public PolygonBaseItem { public: QRectF boundingRect() const override; QPainterPath path; }; /** A text or item label */ class LabelItem : public SceneGraphItem { public: uint8_t renderPhases() const override; QRectF boundingRect() const override; QPointF pos; QString text; QColor color; QFont font; double casingWidth = 0.0; QColor casingColor = Qt::transparent; double frameWidth = 0.0; QColor frameColor = Qt::transparent; QColor shieldColor = Qt::transparent; mutable QRectF bbox; bool hasFineBbox = false; double angle = 0.0; }; } #endif // KOSMINDOORMAP_SCENEGRAPHITEM_H