/**************************************************************************** ** ** Copyright (C) 2020 Paolo Angelelli ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtLocation module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QDECLARATIVEPOLYGONMAPITEM_P_P_H #define QDECLARATIVEPOLYGONMAPITEM_P_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolygonGeometry : public QGeoMapItemGeometry { public: QGeoMapPolygonGeometry(); inline void setAssumeSimple(bool value) { assumeSimple_ = value; } void updateSourcePoints(const QGeoMap &map, const QList &path); void updateScreenPoints(const QGeoMap &map, qreal strokeWidth = 0.0); protected: QPainterPath srcPath_; bool assumeSimple_; }; class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolygonGeometryOpenGL : public QGeoMapItemGeometry { public: typedef struct { QList wrappedBboxes; } WrappedPolygon; QGeoMapPolygonGeometryOpenGL(); ~QGeoMapPolygonGeometryOpenGL() override {} // Temporary method for compatibility in MapCircleObject. Remove when MapObjects are ported. void updateSourcePoints(const QGeoMap &map, const QList &path); void updateSourcePoints(const QGeoMap &map, const QList &perimeter); void updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly); void updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect); void updateScreenPoints(const QGeoMap &map, qreal strokeWidth = 0.0, const QColor &strokeColor = Qt::transparent); void updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth = 0.0); void allocateAndFillPolygon(QSGGeometry *geom) const { const QVector &vx = m_screenVertices; const QVector &ix = m_screenIndices; geom->allocate(vx.size(), ix.size()); if (geom->indexType() == QSGGeometry::UnsignedShortType) { quint16 *its = geom->indexDataAsUShort(); for (int i = 0; i < ix.size(); ++i) its[i] = ix[i]; } else if (geom->indexType() == QSGGeometry::UnsignedIntType) { quint32 *its = geom->indexDataAsUInt(); for (int i = 0; i < ix.size(); ++i) its[i] = ix[i]; } QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); for (int i = 0; i < vx.size(); ++i) pts[i].set(vx[i].x, vx[i].y); } QVector m_screenVertices; QVector m_screenIndices; QDoubleVector2D m_bboxLeftBoundWrapped; QVector m_wrappedPolygons; int m_wrapOffset; }; class Q_LOCATION_PRIVATE_EXPORT MapPolygonShader : public QSGMaterialShader { public: MapPolygonShader(); const char *vertexShader() const override { return "attribute highp vec4 vertex; \n" "uniform highp mat4 qt_Matrix; \n" "uniform highp mat4 mapProjection; \n" "uniform highp vec3 center; \n" "uniform highp vec3 center_lowpart; \n" "uniform lowp float wrapOffset; \n" "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n" "void main() { \n" " vec4 vtx = wrapped(vertex) - vec4(center, 0.0); \n" " vtx = vtx - vec4(center_lowpart, 0.0); \n" " gl_Position = qt_Matrix * mapProjection * vtx; \n" "}"; } const char *fragmentShader() const override { return "uniform lowp vec4 color; \n" "void main() { \n" " gl_FragColor = color; \n" "}"; } void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; char const *const *attributeNames() const override { static char const *const attr[] = { "vertex", nullptr }; return attr; } private: void initialize() override { m_matrix_id = program()->uniformLocation("qt_Matrix"); m_color_id = program()->uniformLocation("color"); m_mapProjection_id = program()->uniformLocation("mapProjection"); m_center_id = program()->uniformLocation("center"); m_center_lowpart_id = program()->uniformLocation("center_lowpart"); m_wrapOffset_id = program()->uniformLocation("wrapOffset"); } int m_center_id; int m_center_lowpart_id; int m_mapProjection_id; int m_matrix_id; int m_color_id; int m_wrapOffset_id; }; class Q_LOCATION_PRIVATE_EXPORT MapPolygonMaterial : public QSGFlatColorMaterial { public: MapPolygonMaterial() : QSGFlatColorMaterial() { // Passing RequiresFullMatrix is essential in order to prevent the // batch renderer from baking in simple, translate-only transforms into // the vertex data. The shader will rely on the fact that // vertexCoord.xy is the Shape-space coordinate and so no modifications // are welcome. setFlag(Blending | RequiresFullMatrix | CustomCompileStep); } QSGMaterialShader *createShader() const override; void setGeoProjection(const QMatrix4x4 &p) { m_geoProjection = p; } QMatrix4x4 geoProjection() const { return m_geoProjection; } void setCenter(const QDoubleVector3D &c) { m_center = c; } QDoubleVector3D center() const { return m_center; } int wrapOffset() const { return m_wrapOffset; } void setWrapOffset(int wrapOffset) { m_wrapOffset = wrapOffset; } int compare(const QSGMaterial *other) const override; QSGMaterialType *type() const override; protected: QMatrix4x4 m_geoProjection; QDoubleVector3D m_center; int m_wrapOffset = 0; }; class Q_LOCATION_PRIVATE_EXPORT MapPolygonNode : public MapItemGeometryNode { public: MapPolygonNode(); ~MapPolygonNode() override; void update(const QColor &fillColor, const QColor &borderColor, const QGeoMapItemGeometry *fillShape, const QGeoMapItemGeometry *borderShape); private: QSGFlatColorMaterial fill_material_; MapPolylineNode *border_; QSGGeometry geometry_; }; class Q_LOCATION_PRIVATE_EXPORT MapPolygonNodeGL : public MapItemGeometryNode { public: MapPolygonNodeGL(); ~MapPolygonNodeGL() override; void update(const QColor &fillColor, const QGeoMapPolygonGeometryOpenGL *fillShape, const QMatrix4x4 &geoProjection, const QDoubleVector3D ¢er); MapPolygonMaterial fill_material_; QSGGeometry geometry_; }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolygonMapItemPrivate { public: QDeclarativePolygonMapItemPrivate(QDeclarativePolygonMapItem &polygon) : m_poly(polygon) { } QDeclarativePolygonMapItemPrivate(QDeclarativePolygonMapItemPrivate &other) : m_poly(other.m_poly) { } virtual ~QDeclarativePolygonMapItemPrivate(); virtual void onLinePropertiesChanged() = 0; virtual void markSourceDirtyAndUpdate() = 0; virtual void onMapSet() = 0; virtual void onGeoGeometryChanged() = 0; virtual void onGeoGeometryUpdated() = 0; virtual void onItemGeometryChanged() = 0; virtual void updatePolish() = 0; virtual void afterViewportChanged() = 0; virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) = 0; virtual bool contains(const QPointF &point) const = 0; QDeclarativePolygonMapItem &m_poly; }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolygonMapItemPrivateCPU: public QDeclarativePolygonMapItemPrivate { public: QDeclarativePolygonMapItemPrivateCPU(QDeclarativePolygonMapItem &polygon) : QDeclarativePolygonMapItemPrivate(polygon) { } QDeclarativePolygonMapItemPrivateCPU(QDeclarativePolygonMapItemPrivate &other) : QDeclarativePolygonMapItemPrivate(other) { } ~QDeclarativePolygonMapItemPrivateCPU() override; void onLinePropertiesChanged() override { // mark dirty just in case we're a width change markSourceDirtyAndUpdate(); } void markSourceDirtyAndUpdate() override { // preserveGeometry is cleared in updateMapItemPaintNode m_geometry.markSourceDirty(); m_borderGeometry.markSourceDirty(); m_poly.polishAndUpdate(); } void regenerateCache() { if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; const QGeoProjectionWebMercator &p = static_cast(m_poly.map()->geoProjection()); m_geopathProjected.clear(); m_geopathProjected.reserve(m_poly.m_geopoly.size()); for (const QGeoCoordinate &c : m_poly.m_geopoly.path()) m_geopathProjected << p.geoToMapProjection(c); } void updateCache() { if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; const QGeoProjectionWebMercator &p = static_cast(m_poly.map()->geoProjection()); m_geopathProjected << p.geoToMapProjection(m_poly.m_geopoly.path().last()); } void preserveGeometry() { m_geometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft()); m_borderGeometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft()); } void afterViewportChanged() override { // preserveGeometry is cleared in updateMapItemPaintNode preserveGeometry(); markSourceDirtyAndUpdate(); } void onMapSet() override { regenerateCache(); markSourceDirtyAndUpdate(); } void onGeoGeometryChanged() override { regenerateCache(); preserveGeometry(); markSourceDirtyAndUpdate(); } void onGeoGeometryUpdated() override { updateCache(); preserveGeometry(); markSourceDirtyAndUpdate(); } void onItemGeometryChanged() override { onGeoGeometryChanged(); } void updatePolish() override { if (m_poly.m_geopoly.path().length() == 0) { // Possibly cleared m_geometry.clear(); m_borderGeometry.clear(); m_poly.setWidth(0); m_poly.setHeight(0); return; } const QGeoMap *map = m_poly.map(); const qreal borderWidth = m_poly.m_border.width(); const QGeoProjectionWebMercator &p = static_cast(map->geoProjection()); QScopedValueRollback rollback(m_poly.m_updatingGeometry); m_poly.m_updatingGeometry = true; m_geometry.updateSourcePoints(*map, m_geopathProjected); m_geometry.updateScreenPoints(*map, borderWidth); QList geoms; geoms << &m_geometry; m_borderGeometry.clear(); if (m_poly.m_border.color().alpha() != 0 && borderWidth > 0) { QList closedPath = m_geopathProjected; closedPath << closedPath.first(); m_borderGeometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft()); const QGeoCoordinate &geometryOrigin = m_geometry.origin(); m_borderGeometry.srcPoints_.clear(); m_borderGeometry.srcPointTypes_.clear(); QDoubleVector2D borderLeftBoundWrapped; QList > clippedPaths = m_borderGeometry.clipPath(*map, closedPath, borderLeftBoundWrapped); if (clippedPaths.size()) { borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); m_borderGeometry.pathToScreen(*map, clippedPaths, borderLeftBoundWrapped); m_borderGeometry.updateScreenPoints(*map, borderWidth); geoms << &m_borderGeometry; } else { m_borderGeometry.clear(); } } QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); m_poly.setWidth(combined.width() + 2 * borderWidth); m_poly.setHeight(combined.height() + 2 * borderWidth); m_poly.setPositionOnMap(m_geometry.origin(), -1 * m_geometry.sourceBoundingBox().topLeft() + QPointF(borderWidth, borderWidth)); } QSGNode *updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override { Q_UNUSED(data); if (!m_node || !oldNode) { m_node = new MapPolygonNode(); if (oldNode) { delete oldNode; oldNode = nullptr; } } else { m_node = static_cast(oldNode); } //TODO: update only material if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_poly.m_dirtyMaterial || !oldNode) { m_node->update(m_poly.m_color, m_poly.m_border.color(), &m_geometry, &m_borderGeometry); m_geometry.setPreserveGeometry(false); m_borderGeometry.setPreserveGeometry(false); m_geometry.markClean(); m_borderGeometry.markClean(); m_poly.m_dirtyMaterial = false; } return m_node; } bool contains(const QPointF &point) const override { return (m_geometry.contains(point) || m_borderGeometry.contains(point)); } QList m_geopathProjected; QGeoMapPolygonGeometry m_geometry; QGeoMapPolylineGeometry m_borderGeometry; MapPolygonNode *m_node = nullptr; }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolygonMapItemPrivateOpenGL: public QDeclarativePolygonMapItemPrivate { public: struct RootNode : public QSGNode /*QSGTransformNode*/, public VisibleNode { RootNode() { } bool isSubtreeBlocked() const override { return subtreeBlocked(); } }; QDeclarativePolygonMapItemPrivateOpenGL(QDeclarativePolygonMapItem &polygon) : QDeclarativePolygonMapItemPrivate(polygon) { } QDeclarativePolygonMapItemPrivateOpenGL(QDeclarativePolygonMapItemPrivate &other) : QDeclarativePolygonMapItemPrivate(other) { } ~QDeclarativePolygonMapItemPrivateOpenGL() override; void markScreenDirtyAndUpdate() { // preserveGeometry is cleared in updateMapItemPaintNode m_geometry.markScreenDirty(); m_borderGeometry.markScreenDirty(); m_poly.polishAndUpdate(); } void onLinePropertiesChanged() override { m_poly.m_dirtyMaterial = true; afterViewportChanged(); } void markSourceDirtyAndUpdate() override { // preserveGeometry is cleared in updateMapItemPaintNode m_geometry.markSourceDirty(); m_borderGeometry.markSourceDirty(); m_poly.polishAndUpdate(); } void preserveGeometry() { m_geometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft()); m_borderGeometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft()); } void afterViewportChanged() override // This is called when the camera changes, or visibleArea changes. { // preserveGeometry is cleared in updateMapItemPaintNode preserveGeometry(); markScreenDirtyAndUpdate(); } void onMapSet() override { markSourceDirtyAndUpdate(); } void onGeoGeometryChanged() override { preserveGeometry(); markSourceDirtyAndUpdate(); } void onGeoGeometryUpdated() override { preserveGeometry(); markSourceDirtyAndUpdate(); } void onItemGeometryChanged() override { onGeoGeometryChanged(); } void updatePolish() override { if (m_poly.m_geopoly.path().length() == 0) { // Possibly cleared m_geometry.clear(); m_borderGeometry.clear(); m_poly.setWidth(0); m_poly.setHeight(0); return; } QScopedValueRollback rollback(m_poly.m_updatingGeometry); m_poly.m_updatingGeometry = true; const qreal lineWidth = m_poly.m_border.width(); const QColor &lineColor = m_poly.m_border.color(); const QColor &fillColor = m_poly.color(); if (fillColor.alpha() != 0) { m_geometry.updateSourcePoints(*m_poly.map(), m_poly.m_geopoly); m_geometry.markScreenDirty(); m_geometry.updateScreenPoints(*m_poly.map(), lineWidth, lineColor); } else { m_geometry.clearBounds(); } QGeoMapItemGeometry * geom = &m_geometry; m_borderGeometry.clearScreen(); if (lineColor.alpha() != 0 && lineWidth > 0) { m_borderGeometry.updateSourcePoints(*m_poly.map(), m_poly.m_geopoly); m_borderGeometry.markScreenDirty(); m_borderGeometry.updateScreenPoints(*m_poly.map(), lineWidth); geom = &m_borderGeometry; } m_poly.setWidth(geom->sourceBoundingBox().width()); m_poly.setHeight(geom->sourceBoundingBox().height()); m_poly.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); } QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override { Q_UNUSED(data); if (!m_rootNode || !oldNode) { m_rootNode = new RootNode(); m_node = new MapPolygonNodeGL(); m_rootNode->appendChildNode(m_node); m_polylinenode = new MapPolylineNodeOpenGLExtruded(); m_rootNode->appendChildNode(m_polylinenode); m_rootNode->markDirty(QSGNode::DirtyNodeAdded); if (oldNode) delete oldNode; } else { m_rootNode = static_cast(oldNode); } const QGeoMap *map = m_poly.map(); const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform(); const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator(); if (m_borderGeometry.isScreenDirty()) { /* Do the border update first */ m_polylinenode->update(m_poly.m_border.color(), float(m_poly.m_border.width()), &m_borderGeometry, combinedMatrix, cameraCenter, Qt::SquareCap, true, 30); // No LOD for polygons just yet. // First figure out what to do with holes. m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } else { m_polylinenode->setSubtreeBlocked(true); } if (m_geometry.isScreenDirty()) { m_node->update(m_poly.m_color, &m_geometry, combinedMatrix, cameraCenter); m_geometry.setPreserveGeometry(false); m_geometry.markClean(); } else { m_node->setSubtreeBlocked(true); } m_rootNode->setSubtreeBlocked(false); return m_rootNode; } bool contains(const QPointF &point) const override { const qreal lineWidth = m_poly.m_border.width(); const QColor &lineColor = m_poly.m_border.color(); const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); if (bounds.contains(point)) { QDeclarativeGeoMap *m = m_poly.quickMap(); if (m) { const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_poly, point)); return m_poly.m_geopoly.contains(crd) || m_borderGeometry.contains(m_poly.mapToItem(m_poly.quickMap(), point), m_poly.border()->width(), static_cast(m_poly.map()->geoProjection())); } else { return true; } } return false; } QGeoMapPolygonGeometryOpenGL m_geometry; QGeoMapPolylineGeometryOpenGL m_borderGeometry; RootNode *m_rootNode = nullptr; MapPolygonNodeGL *m_node = nullptr; MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; }; QT_END_NAMESPACE #endif // QDECLARATIVEPOLYGONMAPITEM_P_P_H