diff options
Diffstat (limited to 'src/location')
30 files changed, 4481 insertions, 826 deletions
diff --git a/src/location/declarativemaps/declarativemaps.pri b/src/location/declarativemaps/declarativemaps.pri index 54a0824b..c2afe9a4 100644 --- a/src/location/declarativemaps/declarativemaps.pri +++ b/src/location/declarativemaps/declarativemaps.pri @@ -29,12 +29,18 @@ PRIVATE_HEADERS += \ declarativemaps/qgeomapobject_p.h \ declarativemaps/qgeomapobject_p_p.h \ declarativemaps/qparameterizableobject_p.h \ + declarativemaps/qdeclarativegeomapitemutils_p.h \ + declarativemaps/qdeclarativepolylinemapitem_p_p.h \ + declarativemaps/qdeclarativepolygonmapitem_p_p.h \ + declarativemaps/qdeclarativerectanglemapitem_p_p.h \ + declarativemaps/qdeclarativecirclemapitem_p_p.h \ declarativemaps/qquickgeomapgesturearea_p.h SOURCES += \ declarativemaps/error_messages.cpp \ declarativemaps/locationvaluetypehelper.cpp \ - declarativemaps/qdeclarativecirclemapitem.cpp \ + declarativemaps/qdeclarativepolylinemapitem.cpp \ + declarativemaps/qdeclarativepolygonmapitem.cpp \ declarativemaps/qdeclarativegeocodemodel.cpp \ declarativemaps/qdeclarativegeomaneuver.cpp \ declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp \ @@ -50,14 +56,15 @@ SOURCES += \ declarativemaps/qdeclarativegeoroutemodel.cpp \ declarativemaps/qdeclarativegeoroutesegment.cpp \ declarativemaps/qdeclarativegeoserviceprovider.cpp \ - declarativemaps/qdeclarativepolygonmapitem.cpp \ - declarativemaps/qdeclarativepolylinemapitem.cpp \ + declarativemaps/qdeclarativecirclemapitem.cpp \ declarativemaps/qdeclarativerectanglemapitem.cpp \ declarativemaps/qdeclarativeroutemapitem.cpp \ declarativemaps/qgeomapitemgeometry.cpp \ declarativemaps/qgeomapobject.cpp \ + declarativemaps/qdeclarativegeomapitemutils.cpp \ declarativemaps/qparameterizableobject.cpp \ declarativemaps/qquickgeomapgesturearea.cpp load(qt_build_paths) LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lpoly2tri$$qtPlatformTargetSuffix() -lclip2tri$$qtPlatformTargetSuffix() + diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp index 846fccbf..f0ba122c 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp @@ -57,6 +57,7 @@ #include <sweep/cdt.h> #include <QtPositioning/private/qclipperutils_p.h> +#include "qdeclarativecirclemapitem_p_p.h" QT_BEGIN_NAMESPACE @@ -274,83 +275,36 @@ void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QDoubleVector2D sourceBounds_ = screenBounds_; } -bool QDeclarativeCircleMapItem::crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +struct CircleBackendSelector { - qreal poleLat = 90; - QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); - QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); - // approximate using great circle distance - qreal distanceToNorthPole = center.distanceTo(northPole); - qreal distanceToSouthPole = center.distanceTo(southPole); - if (distanceToNorthPole < distance || distanceToSouthPole < distance) - return true; - return false; -} - -void QDeclarativeCircleMapItem::calculatePeripheralPoints(QList<QGeoCoordinate> &path, - const QGeoCoordinate ¢er, - qreal distance, - int steps, - QGeoCoordinate &leftBound) -{ - // Calculate points based on great-circle distance - // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function - // but tweaked here for computing multiple points - - // pre-calculations - steps = qMax(steps, 3); - qreal centerLon = center.longitude(); - qreal minLon = centerLon; - qreal latRad = QLocationUtils::radians(center.latitude()); - qreal lonRad = QLocationUtils::radians(centerLon); - qreal cosLatRad = std::cos(latRad); - qreal sinLatRad = std::sin(latRad); - qreal ratio = (distance / QLocationUtils::earthMeanRadius()); - qreal cosRatio = std::cos(ratio); - qreal sinRatio = std::sin(ratio); - qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; - qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; - int idx = 0; - for (int i = 0; i < steps; ++i) { - qreal azimuthRad = 2 * M_PI * i / steps; - qreal resultLatRad = std::asin(sinLatRad_x_cosRatio - + cosLatRad_x_sinRatio * std::cos(azimuthRad)); - qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, - cosRatio - sinLatRad * std::sin(resultLatRad)); - qreal lat2 = QLocationUtils::degrees(resultLatRad); - qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); - - path << QGeoCoordinate(lat2, lon2, center.altitude()); - // Consider only points in the left half of the circle for the left bound. - if (azimuthRad > M_PI) { - if (lon2 > centerLon) // if point and center are on different hemispheres - lon2 -= 360; - if (lon2 < minLon) { - minLon = lon2; - idx = i; - } - } + CircleBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativeCircleMapItem::OpenGL : QDeclarativeCircleMapItem::Software; } - leftBound = path.at(idx); -} + QDeclarativeCircleMapItem::Backend backend = QDeclarativeCircleMapItem::Software; +}; + +Q_GLOBAL_STATIC(CircleBackendSelector, mapCircleBackendSelector) QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), - updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), + m_updatingGeometry(false) + , m_d(new QDeclarativeCircleMapItemPrivateCPU(*this)) { + // ToDo: handle envvar, and switch implementation. m_itemType = QGeoMap::MapCircle; setFlag(ItemHasContents, true); - QObject::connect(&border_, SIGNAL(colorChanged(QColor)), - this, SLOT(markSourceDirtyAndUpdate())); - QObject::connect(&border_, SIGNAL(widthChanged(qreal)), - this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&m_border, SIGNAL(colorChanged(QColor)), + this, SLOT(onLinePropertiesChanged())); + QObject::connect(&m_border, SIGNAL(widthChanged(qreal)), + this, SLOT(onLinePropertiesChanged())); // assume that circles are not self-intersecting // to speed up processing // FIXME: unfortunately they self-intersect at the poles due to current drawing method // so the line is commented out until fixed //geometry_.setAssumeSimple(true); - + setBackend(mapCircleBackendSelector->backend); } QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() @@ -371,23 +325,24 @@ QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() */ QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border() { - return &border_; + return &m_border; } void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() { - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); +} + +void QDeclarativeCircleMapItem::onLinePropertiesChanged() +{ + m_d->onLinePropertiesChanged(); } void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) { QDeclarativeGeoMapItemBase::setMap(quickMap,map); - if (!map) - return; - updateCirclePath(); - markSourceDirtyAndUpdate(); + if (map) + m_d->onMapSet(); } /*! @@ -399,18 +354,18 @@ void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *ma */ void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) { - if (circle_.center() == center) + if (m_circle.center() == center) return; - circle_.setCenter(center); - updateCirclePath(); - markSourceDirtyAndUpdate(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), center, m_circle.radius()); + m_circle.setCenter(center); + m_d->onGeoGeometryChanged(); emit centerChanged(center); } QGeoCoordinate QDeclarativeCircleMapItem::center() { - return circle_.center(); + return m_circle.center(); } /*! @@ -421,17 +376,17 @@ QGeoCoordinate QDeclarativeCircleMapItem::center() */ void QDeclarativeCircleMapItem::setColor(const QColor &color) { - if (color_ == color) + if (m_color == color) return; - color_ = color; - dirtyMaterial_ = true; + m_color = color; + m_dirtyMaterial = true; update(); - emit colorChanged(color_); + emit colorChanged(m_color); } QColor QDeclarativeCircleMapItem::color() const { - return color_; + return m_color; } /*! @@ -443,18 +398,18 @@ QColor QDeclarativeCircleMapItem::color() const */ void QDeclarativeCircleMapItem::setRadius(qreal radius) { - if (circle_.radius() == radius) + if (m_circle.radius() == radius) return; - circle_.setRadius(radius); - updateCirclePath(); - markSourceDirtyAndUpdate(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), m_circle.center(), radius); + m_circle.setRadius(radius); + m_d->onGeoGeometryChanged(); emit radiusChanged(radius); } qreal QDeclarativeCircleMapItem::radius() const { - return circle_.radius(); + return m_circle.radius(); } /*! @@ -472,23 +427,7 @@ qreal QDeclarativeCircleMapItem::radius() const */ QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - - MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); - - if (!node) - node = new MapPolygonNode(); - - //TODO: update only material - if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { - node->update(color_, border_.color(), &geometry_, &borderGeometry_); - geometry_.setPreserveGeometry(false); - borderGeometry_.setPreserveGeometry(false); - geometry_.markClean(); - borderGeometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } /*! @@ -498,111 +437,41 @@ void QDeclarativeCircleMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - if (!circle_.isValid()) { - geometry_.clear(); - borderGeometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - QList<QDoubleVector2D> circlePath = circlePath_; - - int pathCount = circlePath.size(); - bool preserve = preserveCircleGeometry(circlePath, circle_.center(), circle_.radius(), p); - // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); - // to fix QTBUG-62154 - geometry_.setPreserveGeometry(true, leftBound_); // to set the geoLeftBound_ - geometry_.setPreserveGeometry(preserve, leftBound_); - - bool invertedCircle = false; - if (crossEarthPole(circle_.center(), circle_.radius()) && circlePath.size() == pathCount) { - geometry_.updateScreenPointsInvert(circlePath, *map()); // invert fill area for really huge circles - invertedCircle = true; - } else { - geometry_.updateSourcePoints(*map(), circlePath); - geometry_.updateScreenPoints(*map(), border_.width()); - } - - borderGeometry_.clear(); - QList<QGeoMapItemGeometry *> geoms; - geoms << &geometry_; - - if (border_.color() != Qt::transparent && border_.width() > 0) { - QList<QDoubleVector2D> closedPath = circlePath; - closedPath << closedPath.first(); - - if (invertedCircle) { - closedPath = circlePath_; - closedPath << closedPath.first(); - std::reverse(closedPath.begin(), closedPath.end()); - } - - borderGeometry_.setPreserveGeometry(true, leftBound_); - borderGeometry_.setPreserveGeometry(preserve, leftBound_); - - // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. - const QGeoCoordinate &geometryOrigin = geometry_.origin(); - - borderGeometry_.srcPoints_.clear(); - borderGeometry_.srcPointTypes_.clear(); - - QDoubleVector2D borderLeftBoundWrapped; - QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); - if (clippedPaths.size()) { - borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); - borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); - borderGeometry_.updateScreenPoints(*map(), border_.width()); - geoms << &borderGeometry_; - } else { - borderGeometry_.clear(); - } - } - - QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); - - if (invertedCircle || !preserve) { - setWidth(combined.width()); - setHeight(combined.height()); - setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); - } else { - setWidth(combined.width() + 2 * border_.width()); - setHeight(combined.height() + 2 * border_.width()); - } - - // No offsetting here, even in normal case, because first point offset is already translated - setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); + m_d->updatePolish(); } /*! \internal + + The OpenGL backend doesn't do circles crossing poles yet. + So if that backend is selected and the circle crosses the poles, use the CPU backend instead. */ -void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +void QDeclarativeCircleMapItem::possiblySwitchBackend(const QGeoCoordinate &oldCenter, qreal oldRadius, const QGeoCoordinate &newCenter, qreal newRadius) { - if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + if (m_backend != QDeclarativeCircleMapItem::OpenGL) return; - markSourceDirtyAndUpdate(); + // if old does not cross and new crosses, move to CPU. + if (!QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { + QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this))); + m_d.swap(d); + } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { // else if old crosses and new does not cross, move back to OpenGL + QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + m_d.swap(d); + } } /*! \internal */ -void QDeclarativeCircleMapItem::updateCirclePath() +void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + if (event.mapSize.isEmpty()) return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - QList<QGeoCoordinate> path; - calculatePeripheralPoints(path, circle_.center(), circle_.radius(), CircleSamples, leftBound_); - circlePath_.clear(); - for (const QGeoCoordinate &c : path) - circlePath_ << p.geoToMapProjection(c); + m_d->afterViewportChanged(); } /*! @@ -610,30 +479,69 @@ void QDeclarativeCircleMapItem::updateCirclePath() */ bool QDeclarativeCircleMapItem::contains(const QPointF &point) const { - return (geometry_.contains(point) || borderGeometry_.contains(point)); + return m_d->contains(point); + // } const QGeoShape &QDeclarativeCircleMapItem::geoShape() const { - return circle_; + return m_circle; } void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) { - if (shape == circle_) + if (shape == m_circle) return; const QGeoCircle circle(shape); // if shape isn't a circle, circle will be created as a default-constructed circle - const bool centerHasChanged = circle.center() != circle_.center(); - const bool radiusHasChanged = circle.radius() != circle_.radius(); - circle_ = circle; + const bool centerHasChanged = circle.center() != m_circle.center(); + const bool radiusHasChanged = circle.radius() != m_circle.radius(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), circle.center(), circle.radius()); + m_circle = circle; - updateCirclePath(); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); if (centerHasChanged) - emit centerChanged(circle_.center()); + emit centerChanged(m_circle.center()); if (radiusHasChanged) - emit radiusChanged(circle_.radius()); + emit radiusChanged(m_circle.radius()); +} + +/*! + \qmlproperty MapCircle.Backend QtLocation::MapCircle::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapCircle.Software and \b{MapCircle.OpenGL}. + The default value is \b{MapCircle.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ + +QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativeCircleMapItem::setBackend(QDeclarativeCircleMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativeCircleMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this)) + : static_cast<QDeclarativeCircleMapItemPrivate * >(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); } /*! @@ -641,21 +549,27 @@ void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) */ void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !circle_.isValid() || updatingGeometry_ || newGeometry == oldGeometry) { + if (!map() || !m_circle.isValid() || m_updatingGeometry || newGeometry == oldGeometry) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } - QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) / 2; + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) * 0.5; QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false); if (newCoordinate.isValid()) - setCenter(newCoordinate); + setCenter(newCoordinate); // ToDo: this is incorrect. setting such center might yield to another geometry changed. // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested // call to this function. } -bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> &path, +QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {} + +QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {} + +QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {} + +bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) { // if circle crosses north/south pole, then don't preserve circular shape, @@ -664,7 +578,6 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> & return false; } return true; - } /* @@ -684,7 +597,7 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> & * | ____ | * \__/ \__/ */ -void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QDoubleVector2D> &path, +void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) { @@ -743,6 +656,66 @@ void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QDoubleVector } } +bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +{ + qreal poleLat = 90; + QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); + QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); + // approximate using great circle distance + qreal distanceToNorthPole = center.distanceTo(northPole); + qreal distanceToSouthPole = center.distanceTo(southPole); + if (distanceToNorthPole < distance || distanceToSouthPole < distance) + return true; + return false; +} + +void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path, + const QGeoCoordinate ¢er, + qreal distance, + int steps, + QGeoCoordinate &leftBound) +{ + // Calculate points based on great-circle distance + // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function + // but tweaked here for computing multiple points + + // pre-calculations + steps = qMax(steps, 3); + qreal centerLon = center.longitude(); + qreal minLon = centerLon; + qreal latRad = QLocationUtils::radians(center.latitude()); + qreal lonRad = QLocationUtils::radians(centerLon); + qreal cosLatRad = std::cos(latRad); + qreal sinLatRad = std::sin(latRad); + qreal ratio = (distance / QLocationUtils::earthMeanRadius()); + qreal cosRatio = std::cos(ratio); + qreal sinRatio = std::sin(ratio); + qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; + qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; + int idx = 0; + for (int i = 0; i < steps; ++i) { + qreal azimuthRad = 2 * M_PI * i / steps; + qreal resultLatRad = std::asin(sinLatRad_x_cosRatio + + cosLatRad_x_sinRatio * std::cos(azimuthRad)); + qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, + cosRatio - sinLatRad * std::sin(resultLatRad)); + qreal lat2 = QLocationUtils::degrees(resultLatRad); + qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); + + path << QGeoCoordinate(lat2, lon2, center.altitude()); + // Consider only points in the left half of the circle for the left bound. + if (azimuthRad > M_PI) { + if (lon2 > centerLon) // if point and center are on different hemispheres + lon2 -= 360; + if (lon2 < minLon) { + minLon = lon2; + idx = i; + } + } + } + leftBound = path.at(idx); +} + ////////////////////////////////////////////////////////////////////// QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem_p.h b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h index 4b3f81c0..a6715e30 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem_p.h +++ b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h @@ -50,33 +50,33 @@ #include <QtLocation/private/qlocationglobal_p.h> #include <QtLocation/private/qdeclarativegeomapitembase_p.h> -#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> -#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p_p.h> #include <QSGGeometryNode> #include <QSGFlatColorMaterial> #include <QtPositioning/QGeoCircle> QT_BEGIN_NAMESPACE -class Q_LOCATION_PRIVATE_EXPORT QGeoMapCircleGeometry : public QGeoMapPolygonGeometry -{ -public: - QGeoMapCircleGeometry(); - - void updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map); -}; - +class QDeclarativeCircleMapItemPrivate; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItem : public QDeclarativeGeoMapItemBase { Q_OBJECT + Q_ENUMS(Backend) + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + Q_PROPERTY(Backend backend READ backend WRITE setBackend NOTIFY backendChanged REVISION 15) public: - explicit QDeclarativeCircleMapItem(QQuickItem *parent = 0); - ~QDeclarativeCircleMapItem(); + enum Backend { + Software = 0, + OpenGL = 1 + }; + + explicit QDeclarativeCircleMapItem(QQuickItem *parent = nullptr); + ~QDeclarativeCircleMapItem() override; virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) override; virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) override; @@ -96,40 +96,41 @@ public: const QGeoShape &geoShape() const override; void setGeoShape(const QGeoShape &shape) override; - static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance); - static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate ¢er, - qreal distance, int steps, QGeoCoordinate &leftBound); - static bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, - qreal distance, const QGeoProjectionWebMercator &p); - static void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, - qreal distance, const QGeoProjectionWebMercator &p); + Backend backend() const; + void setBackend(Backend b); Q_SIGNALS: void centerChanged(const QGeoCoordinate ¢er); void radiusChanged(qreal radius); void colorChanged(const QColor &color); + void backendChanged(); protected: void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; void updatePolish() override; + void possiblySwitchBackend(const QGeoCoordinate &oldCenter, qreal oldRadius, const QGeoCoordinate &newCenter, qreal newRadius); protected Q_SLOTS: void markSourceDirtyAndUpdate(); + void onLinePropertiesChanged(); virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) override; private: void updateCirclePath(); private: - QGeoCircle circle_; - QDeclarativeMapLineProperties border_; - QColor color_; - QList<QDoubleVector2D> circlePath_; - QGeoCoordinate leftBound_; - bool dirtyMaterial_; - QGeoMapCircleGeometry geometry_; - QGeoMapPolylineGeometry borderGeometry_; - bool updatingGeometry_; + QGeoCircle m_circle; + QDeclarativeMapLineProperties m_border; + QColor m_color; + bool m_dirtyMaterial; + bool m_updatingGeometry; + Backend m_backend = Software; + + QScopedPointer<QDeclarativeCircleMapItemPrivate> m_d; + + friend class QDeclarativeCircleMapItemPrivate; + friend class QDeclarativeCircleMapItemPrivateCPU; + friend class QDeclarativeCircleMapItemPrivateOpenGL; }; ////////////////////////////////////////////////////////////////////// diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h b/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h new file mode 100644 index 00000000..71a6b6a4 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h @@ -0,0 +1,448 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 QDECLARATIVECIRCLEMAPITEM_P_P_H +#define QDECLARATIVECIRCLEMAPITEM_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 <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p_p.h> +#include <QtLocation/private/qdeclarativecirclemapitem_p.h> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapCircleGeometry : public QGeoMapPolygonGeometry +{ +public: + QGeoMapCircleGeometry(); + + void updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map); +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivate +{ +public: + static const int CircleSamples = 128; // ToDo: make this radius && ZL dependent? + + QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItem &circle) : m_circle(circle) + { + + } + QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItemPrivate &other) : m_circle(other.m_circle) + { + } + + virtual ~QDeclarativeCircleMapItemPrivate(); + virtual void onLinePropertiesChanged() = 0; + virtual void markSourceDirtyAndUpdate() = 0; + virtual void onMapSet() = 0; + virtual void onGeoGeometryChanged() = 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; + + void updateCirclePath() + { + if (!m_circle.map() || m_circle.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + return; + + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection()); + QList<QGeoCoordinate> path; + calculatePeripheralPoints(path, m_circle.center(), m_circle.radius(), CircleSamples, m_leftBound); + m_circlePath.clear(); + for (const QGeoCoordinate &c : path) + m_circlePath << p.geoToMapProjection(c); + } + + static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance); + + static bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, + qreal distance, const QGeoProjectionWebMercator &p); + static void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, + qreal distance, const QGeoProjectionWebMercator &p); + + static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate ¢er, + qreal distance, int steps, QGeoCoordinate &leftBound); + + QDeclarativeCircleMapItem &m_circle; + QList<QDoubleVector2D> m_circlePath; + QGeoCoordinate m_leftBound; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateCPU: public QDeclarativeCircleMapItemPrivate +{ +public: + + QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) + { + } + + QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItemPrivate &other) + : QDeclarativeCircleMapItemPrivate(other) + { + } + + ~QDeclarativeCircleMapItemPrivateCPU() 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_circle.polishAndUpdate(); + } + void onMapSet() override + { + updateCirclePath(); + markSourceDirtyAndUpdate(); + } + void onGeoGeometryChanged() override + { + updateCirclePath(); + markSourceDirtyAndUpdate(); + } + void onItemGeometryChanged() override + { + onGeoGeometryChanged(); + } + void afterViewportChanged() override + { + markSourceDirtyAndUpdate(); + } + void updatePolish() override + { + if (!m_circle.m_circle.isValid()) { + m_geometry.clear(); + m_borderGeometry.clear(); + m_circle.setWidth(0); + m_circle.setHeight(0); + return; + } + + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection()); + QScopedValueRollback<bool> rollback(m_circle.m_updatingGeometry); + m_circle.m_updatingGeometry = true; + + QList<QDoubleVector2D> circlePath = m_circlePath; + + int pathCount = circlePath.size(); + bool preserve = preserveCircleGeometry(circlePath, m_circle.m_circle.center(), m_circle.m_circle.radius(), p); + // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); + // to fix QTBUG-62154 + m_geometry.setPreserveGeometry(true, m_leftBound); // to set the geoLeftBound_ + m_geometry.setPreserveGeometry(preserve, m_leftBound); + + bool invertedCircle = false; + if (crossEarthPole(m_circle.m_circle.center(), m_circle.m_circle.radius()) && circlePath.size() == pathCount) { + m_geometry.updateScreenPointsInvert(circlePath, *m_circle.map()); // invert fill area for really huge circles + invertedCircle = true; + } else { + m_geometry.updateSourcePoints(*m_circle.map(), circlePath); + m_geometry.updateScreenPoints(*m_circle.map(), m_circle.m_border.width()); + } + + m_borderGeometry.clear(); + QList<QGeoMapItemGeometry *> geoms; + geoms << &m_geometry; + + if (m_circle.m_border.color() != Qt::transparent && m_circle.m_border.width() > 0) { + QList<QDoubleVector2D> closedPath = circlePath; + closedPath << closedPath.first(); + + if (invertedCircle) { + closedPath = m_circlePath; + closedPath << closedPath.first(); + std::reverse(closedPath.begin(), closedPath.end()); + } + + m_borderGeometry.setPreserveGeometry(true, m_leftBound); + m_borderGeometry.setPreserveGeometry(preserve, m_leftBound); + + // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. + const QGeoCoordinate &geometryOrigin = m_geometry.origin(); + + m_borderGeometry.srcPoints_.clear(); + m_borderGeometry.srcPointTypes_.clear(); + + QDoubleVector2D borderLeftBoundWrapped; + QList<QList<QDoubleVector2D > > clippedPaths = m_borderGeometry.clipPath(*m_circle.map(), closedPath, borderLeftBoundWrapped); + if (clippedPaths.size()) { + borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); + m_borderGeometry.pathToScreen(*m_circle.map(), clippedPaths, borderLeftBoundWrapped); + m_borderGeometry.updateScreenPoints(*m_circle.map(), m_circle.m_border.width()); + geoms << &m_borderGeometry; + } else { + m_borderGeometry.clear(); + } + } + + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + + if (invertedCircle || !preserve) { + m_circle.setWidth(combined.width()); + m_circle.setHeight(combined.height()); + } else { + m_circle.setWidth(combined.width() + 2 * m_circle.m_border.width()); // ToDo: Fix this! + m_circle.setHeight(combined.height() + 2 * m_circle.m_border.width()); + } + + // No offsetting here, even in normal case, because first point offset is already translated + m_circle.setPositionOnMap(m_geometry.origin(), m_geometry.firstPointOffset()); + } + + QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override + { + Q_UNUSED(data); + if (!m_node || !oldNode) { // Apparently the QSG might delete the nodes if they become invisible + m_node = new MapPolygonNode(); + if (oldNode) { + delete oldNode; + oldNode = nullptr; + } + } else { + m_node = static_cast<MapPolygonNode *>(oldNode); + } + + //TODO: update only material + if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_circle.m_dirtyMaterial) { + m_node->update(m_circle.m_color, m_circle.m_border.color(), &m_geometry, &m_borderGeometry); + m_geometry.setPreserveGeometry(false); + m_borderGeometry.setPreserveGeometry(false); + m_geometry.markClean(); + m_borderGeometry.markClean(); + m_circle.m_dirtyMaterial = false; + } + return m_node; + } + bool contains(const QPointF &point) const override + { + return (m_geometry.contains(point) || m_borderGeometry.contains(point)); + } + + QGeoMapCircleGeometry m_geometry; + QGeoMapPolylineGeometry m_borderGeometry; + MapPolygonNode *m_node = nullptr; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateOpenGL: public QDeclarativeCircleMapItemPrivate +{ +public: + QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) + { + } + + QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate &other) + : QDeclarativeCircleMapItemPrivate(other) + { + } + + ~QDeclarativeCircleMapItemPrivateOpenGL() override; + + void onLinePropertiesChanged() override + { + m_circle.m_dirtyMaterial = true; + afterViewportChanged(); + } + void markScreenDirtyAndUpdate() + { + // preserveGeometry is cleared in updateMapItemPaintNode + m_geometry.markScreenDirty(); + m_borderGeometry.markScreenDirty(); + m_circle.polishAndUpdate(); + } + virtual void markSourceDirtyAndUpdate() override + { + updateCirclePath(); + preserveGeometry(); + m_geometry.markSourceDirty(); + m_borderGeometry.markSourceDirty(); + m_circle.polishAndUpdate(); + } + void preserveGeometry() + { + m_geometry.setPreserveGeometry(true, m_leftBound); + m_borderGeometry.setPreserveGeometry(true, m_leftBound); + } + virtual void onMapSet() override + { + markSourceDirtyAndUpdate(); + } + virtual void onGeoGeometryChanged() override + { + + markSourceDirtyAndUpdate(); + } + virtual void onItemGeometryChanged() override + { + onGeoGeometryChanged(); + } + virtual void afterViewportChanged() override + { + preserveGeometry(); + markScreenDirtyAndUpdate(); + } + virtual void updatePolish() override + { + if (m_circle.m_circle.isEmpty()) { + m_geometry.clear(); + m_borderGeometry.clear(); + m_circle.setWidth(0); + m_circle.setHeight(0); + return; + } + + QScopedValueRollback<bool> rollback(m_circle.m_updatingGeometry); + m_circle.m_updatingGeometry = true; + const qreal lineWidth = m_circle.m_border.width(); + const QColor &lineColor = m_circle.m_border.color(); + const QColor &fillColor = m_circle.color(); + if (fillColor.alpha() != 0) { + m_geometry.updateSourcePoints(*m_circle.map(), m_circlePath); + m_geometry.markScreenDirty(); + m_geometry.updateScreenPoints(*m_circle.map(), lineWidth, lineColor); + } else { + m_geometry.clearBounds(); + } + + QGeoMapItemGeometry * geom = &m_geometry; + m_borderGeometry.clearScreen(); + if (lineColor.alpha() != 0 && lineWidth > 0) { + m_borderGeometry.updateSourcePoints(*m_circle.map(), m_circle.m_circle); + m_borderGeometry.markScreenDirty(); + m_borderGeometry.updateScreenPoints(*m_circle.map(), lineWidth); + geom = &m_borderGeometry; + } + m_circle.setWidth(geom->sourceBoundingBox().width()); + m_circle.setHeight(geom->sourceBoundingBox().height()); + m_circle.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); + } + + virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override + { + Q_UNUSED(data); + + if (!m_rootNode || !oldNode) { + m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::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<QDeclarativePolygonMapItemPrivateOpenGL::RootNode *>(oldNode); + } + + const QGeoMap *map = m_circle.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_circle.m_border.color(), + float(m_circle.m_border.width()), + &m_borderGeometry, + combinedMatrix, + cameraCenter, + Qt::SquareCap, + true); + m_borderGeometry.setPreserveGeometry(false); + m_borderGeometry.markClean(); + } else { + m_polylinenode->setSubtreeBlocked(true); + } + if (m_geometry.isScreenDirty()) { + m_node->update(m_circle.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; + } + virtual bool contains(const QPointF &point) const override + { + const qreal lineWidth = m_circle.m_border.width(); + const QColor &lineColor = m_circle.m_border.color(); + const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); + if (bounds.contains(point)) { + QDeclarativeGeoMap *m = m_circle.quickMap(); + if (m) { + const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_circle, point)); + return m_circle.m_circle.contains(crd) || m_borderGeometry.contains(m_circle.mapToItem(m_circle.quickMap(), point), + m_circle.border()->width(), + static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection())); + } else { + return true; + } + } + return false; + } + + QGeoMapPolygonGeometryOpenGL m_geometry; + QGeoMapPolylineGeometryOpenGL m_borderGeometry; + QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr; + MapPolygonNodeGL *m_node = nullptr; + MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVECIRCLEMAPITEM_P_P_H diff --git a/src/location/declarativemaps/qdeclarativegeomap.cpp b/src/location/declarativemaps/qdeclarativegeomap.cpp index 6a2d82e8..fc282829 100644 --- a/src/location/declarativemaps/qdeclarativegeomap.cpp +++ b/src/location/declarativemaps/qdeclarativegeomap.cpp @@ -198,7 +198,7 @@ QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) setAcceptHoverEvents(false); setAcceptedMouseButtons(Qt::LeftButton); setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape); - setFiltersChildMouseEvents(true); + setFiltersChildMouseEvents(true); // needed for childMouseEventFilter to work. m_activeMapType = new QDeclarativeGeoMapType(QGeoMapType(QGeoMapType::NoMap, tr("No Map"), @@ -707,7 +707,8 @@ void QDeclarativeGeoMap::mappingManagerInitialized() emit m_map->copyrightsChanged(copyrightImage); - connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QQuickItem::update); + connect(window(), &QQuickWindow::beforeSynchronizing, this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection); + connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QDeclarativeGeoMap::onSGNodeChanged); connect(m_map.data(), &QGeoMap::cameraCapabilitiesChanged, this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged); // This prefetches a buffer around the map @@ -2175,6 +2176,38 @@ bool QDeclarativeGeoMap::removeMapItemView_real(QDeclarativeGeoMapItemView *item return removeMapItemGroup_real(itemView); // at this point, all delegate instances have been removed. } +void QDeclarativeGeoMap::updateItemToWindowTransform() +{ + if (!m_initialized) + return; + + // Update itemToWindowTransform into QGeoProjection + const QTransform item2WindowOld = m_map->geoProjection().itemToWindowTransform(); + QTransform item2Window = QQuickItemPrivate::get(this)->itemToWindowTransform(); + if (!property("layer").isNull() && property("layer").value<QObject *>()->property("enabled").toBool()) + item2Window.reset(); // When layer is enabled, the item is rendered offscreen with no transformation, then the layer is applied + + m_map->setItemToWindowTransform(item2Window); + + // This method is called at every redraw, including those redraws not generated by + // sgNodeChanged. + // In these cases, *if* the item2windowTransform has changed (e.g., if transformation of + // the item or one of its ancestors changed), a forced update of the map items using accelerated + // GL implementation has to be performed in order to have them pulling the updated itemToWindowTransform. + if (!m_sgNodeHasChanged && item2WindowOld != item2Window) { + for (auto i: qAsConst(m_mapItems)) + i->setMaterialDirty(); + } + + m_sgNodeHasChanged = false; +} + +void QDeclarativeGeoMap::onSGNodeChanged() +{ + m_sgNodeHasChanged = true; + update(); +} + /*! \qmlmethod void QtLocation::Map::addMapItemView(MapItemView itemView) diff --git a/src/location/declarativemaps/qdeclarativegeomap_p.h b/src/location/declarativemaps/qdeclarativegeomap_p.h index ee9f8ec2..7dcb60aa 100644 --- a/src/location/declarativemaps/qdeclarativegeomap_p.h +++ b/src/location/declarativemaps/qdeclarativegeomap_p.h @@ -267,6 +267,8 @@ protected: bool removeMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup); bool addMapItemView_real(QDeclarativeGeoMapItemView *itemView); bool removeMapItemView_real(QDeclarativeGeoMapItemView *itemView); + void updateItemToWindowTransform(); + void onSGNodeChanged(); private Q_SLOTS: void mappingManagerInitialized(); @@ -308,6 +310,7 @@ private: double m_maximumViewportLatitude; double m_minimumViewportLatitude = 0.0; bool m_initialized; + bool m_sgNodeHasChanged = false; QList<QDeclarativeGeoMapParameter *> m_mapParameters; QList<QGeoMapObject*> m_pendingMapObjects; // Used only in the initialization phase QGeoCameraCapabilities m_cameraCapabilities; diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp index 23993faf..d1a34f1e 100644 --- a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp +++ b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp @@ -323,6 +323,8 @@ bool QDeclarativeGeoMapItemBase::isPolishScheduled() const return QQuickItemPrivate::get(this)->polishScheduled; } +void QDeclarativeGeoMapItemBase::setMaterialDirty() {} + void QDeclarativeGeoMapItemBase::polishAndUpdate() { polish(); diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase_p.h b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h index 38a118e5..65f111f6 100644 --- a/src/location/declarativemaps/qdeclarativegeomapitembase_p.h +++ b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h @@ -92,8 +92,8 @@ public: virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map); virtual void setPositionOnMap(const QGeoCoordinate &coordinate, const QPointF &offset); - QDeclarativeGeoMap *quickMap() { return quickMap_; } - QGeoMap *map() { return map_; } + QDeclarativeGeoMap *quickMap() const { return quickMap_; } + QGeoMap *map() const { return map_; } virtual const QGeoShape &geoShape() const = 0; virtual void setGeoShape(const QGeoShape &shape) = 0; @@ -108,6 +108,23 @@ public: void setParentGroup(QDeclarativeGeoMapItemGroup &parentGroup); + template <typename T = QObject> + + QList<T*> quickChildren() const + { + QList<T*> res; + QObjectList kids = children(); + QList<QQuickItem *> quickKids = childItems(); + for (int i = 0; i < quickKids.count(); ++i) + kids.append(quickKids.at(i)); + for (auto kid : qAsConst(kids)) { + auto val = qobject_cast<T*>(kid); + if (val) + res.push_back(val); + } + return res; + } + Q_SIGNALS: void mapItemOpacityChanged(); Q_REVISION(12) void addTransitionFinished(); @@ -122,6 +139,7 @@ protected: float zoomLevelOpacity() const; bool childMouseEventFilter(QQuickItem *item, QEvent *event); bool isPolishScheduled() const; + virtual void setMaterialDirty(); QGeoMap::ItemType m_itemType = QGeoMap::NoItem; diff --git a/src/location/declarativemaps/qdeclarativegeomapitemutils.cpp b/src/location/declarativemaps/qdeclarativegeomapitemutils.cpp new file mode 100644 index 00000000..e2774559 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemutils.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitemutils_p.h" +#include <QtPositioning/private/qdoublevector3d_p.h> +#include <QtPositioning/private/qdoublematrix4x4_p.h> +#include <QtPositioning/QGeoCoordinate> +#include <QPointF> +#include <QMatrix4x4> +#include <QPainterPath> +#include <QPainterPathStroker> +#include <QtGui/private/qvectorpath_p.h> +#include <QtGui/private/qtriangulatingstroker_p.h> +#include <QtGui/private/qtriangulator_p.h> +#include <QtPositioning/private/qclipperutils_p.h> + +QT_BEGIN_NAMESPACE + +void QDeclarativeGeoMapItemUtils::wrapPath(const QList<QGeoCoordinate> &perimeter, + const QGeoCoordinate &geoLeftBound, + const QGeoProjectionWebMercator &p, + QList<QDoubleVector2D> &wrappedPath, + QList<QDoubleVector2D> &wrappedPathMinus1, + QList<QDoubleVector2D> &wrappedPathPlus1, + QDoubleVector2D *leftBoundWrapped) +{ + QList<QDoubleVector2D> path; + for (const QGeoCoordinate &c : perimeter) + path << p.geoToMapProjection(c); + const QDoubleVector2D leftBound = p.geoToMapProjection(geoLeftBound); + wrappedPath.clear(); + wrappedPathPlus1.clear(); + wrappedPathMinus1.clear(); + // compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D coord = path.at(i); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(coord.x()) || !qIsFinite(coord.y())) + return; + + const bool isPointLessThanUnwrapBelowX = (coord.x() < leftBound.x()); + // unwrap x to preserve geometry if moved to border of map + if (isPointLessThanUnwrapBelowX) + coord.setX(coord.x() + 1.0); + + QDoubleVector2D coordP1(coord.x() + 1.0, coord.y()); + QDoubleVector2D coordM1(coord.x() - 1.0, coord.y()); + + wrappedPath.append(coord); + wrappedPathPlus1.append(coordP1); + wrappedPathMinus1.append(coordM1); + } + if (leftBoundWrapped) + *leftBoundWrapped = leftBound; +} + +void QDeclarativeGeoMapItemUtils::wrapPath(const QList<QGeoCoordinate> &perimeter, + const QGeoCoordinate &geoLeftBound, + const QGeoProjectionWebMercator &p, + QList<QDoubleVector2D> &wrappedPath, + QDoubleVector2D *leftBoundWrapped) +{ + QList<QDoubleVector2D> path; + for (const QGeoCoordinate &c : perimeter) + path << p.geoToMapProjection(c); + const QDoubleVector2D leftBound = p.geoToMapProjection(geoLeftBound); + wrapPath(path, leftBound,wrappedPath); + if (leftBoundWrapped) + *leftBoundWrapped = leftBound; +} + +void QDeclarativeGeoMapItemUtils::wrapPath(const QList<QDoubleVector2D> &path, + const QDoubleVector2D &geoLeftBound, + QList<QDoubleVector2D> &wrappedPath) +{ + wrappedPath.clear(); + // compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D coord = path.at(i); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(coord.x()) || !qIsFinite(coord.y())) + return; + + const bool isPointLessThanUnwrapBelowX = (coord.x() < geoLeftBound.x()); + // unwrap x to preserve geometry if moved to border of map + if (isPointLessThanUnwrapBelowX) + coord.setX(coord.x() + 1.0); + + wrappedPath.append(coord); + } +} + +void QDeclarativeGeoMapItemUtils::clipPolygon(const QList<QDoubleVector2D> &wrappedPath, const QGeoProjectionWebMercator &p, QList<QList<QDoubleVector2D> > &clippedPaths, QDoubleVector2D *leftBoundWrapped, const bool closed) +{ + // 2) Clip bounding box + clippedPaths.clear(); + const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry(); + if (visibleRegion.size()) { + c2t::clip2tri clipper; + clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), closed); + clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion)); + Paths res = clipper.execute(c2t::clip2tri::Intersection, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd); + clippedPaths = QClipperUtils::pathsToQList(res); + + if (leftBoundWrapped) { + // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X + QDoubleVector2D lb(qInf(), qInf()); + for (const QList<QDoubleVector2D> &path: clippedPaths) + for (const QDoubleVector2D &p: path) + if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) + // y-minimization needed to find the same point on polygon and border + lb = p; + + if (qIsInf(lb.x())) // e.g., when the polygon is clipped entirely + return; + + // 2.2) Prevent the conversion to and from clipper from introducing tiny negative offsets which, + // in turn will make the geometry wrap around. + lb.setX(qMax(leftBoundWrapped->x(), lb.x())); + + *leftBoundWrapped = lb; + // srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb)); + } + } else { + clippedPaths.append(wrappedPath); + } +} + +void QDeclarativeGeoMapItemUtils::projectBbox(const QList<QDoubleVector2D> &clippedBbox, const QGeoProjectionWebMercator &p, QPainterPath &projectedBbox) +{ + projectedBbox = QPainterPath(); // clear() is added in 5.13.. + for (int i = 0; i < clippedBbox.size(); ++i) { + QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(clippedBbox.at(i)); + if (i == 0) + projectedBbox.moveTo(point.toPointF()); + else + projectedBbox.lineTo(point.toPointF()); + } + projectedBbox.closeSubpath(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapitemutils_p.h b/src/location/declarativemaps/qdeclarativegeomapitemutils_p.h new file mode 100644 index 00000000..48ece6b2 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemutils_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 QDECLARATIVEGEOMAPITEMUTILS_P_H +#define QDECLARATIVEGEOMAPITEMUTILS_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 <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qgeoprojection_p.h> +#include <QtPositioning/private/qdoublevector2d_p.h> + + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapItemUtils +{ +public: + struct vec2 { + float x; + float y; + vec2(const QDoubleVector2D &p) + { + x = float(p.x()); + y = float(p.y()); + } + vec2() = default; + vec2 &operator=(const QPointF &p) + { + x = float(p.x()); + y = float(p.y()); + return *this; + } + vec2 &operator=(const QDoubleVector2D &p) + { + x = float(p.x()); + y = float(p.y()); + return *this; + } + QDoubleVector2D toDoubleVector2D() const + { + return QDoubleVector2D(double(x), double(y)); + } + }; + + static void wrapPath(const QList<QGeoCoordinate> &perimeter + ,const QGeoCoordinate &geoLeftBound + ,const QGeoProjectionWebMercator &p + ,QList<QDoubleVector2D> &wrappedPath + ,QList<QDoubleVector2D> &wrappedPathMinus1 + ,QList<QDoubleVector2D> &wrappedPathPlus1 + ,QDoubleVector2D *leftBoundWrapped = nullptr); + + static void wrapPath(const QList<QGeoCoordinate> &perimeter + ,const QGeoCoordinate &geoLeftBound + ,const QGeoProjectionWebMercator &p + ,QList<QDoubleVector2D> &wrappedPath + ,QDoubleVector2D *leftBoundWrapped = nullptr); + + static void wrapPath(const QList<QDoubleVector2D> &path + , const QDoubleVector2D &geoLeftBound + , QList<QDoubleVector2D> &wrappedPath); + + + static void clipPolygon(const QList<QDoubleVector2D> &wrappedPath + ,const QGeoProjectionWebMercator &p + ,QList<QList<QDoubleVector2D> > &clippedPaths + ,QDoubleVector2D *leftBoundWrapped = nullptr + ,const bool closed = true); + + static void projectBbox(const QList<QDoubleVector2D> &clippedBbox + ,const QGeoProjectionWebMercator &p + ,QPainterPath &projectedBbox); + +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEGEOMAPITEMUTILS_P_H diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp index 86ff04af..fa6ee174 100644 --- a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp @@ -34,7 +34,11 @@ ** ****************************************************************************/ +#include "qdeclarativegeomapitemutils_p.h" #include "qdeclarativepolygonmapitem_p.h" +#include "qdeclarativepolylinemapitem_p_p.h" +#include "qdeclarativepolygonmapitem_p_p.h" +#include "qdeclarativerectanglemapitem_p_p.h" #include "qlocationutils_p.h" #include "error_messages_p.h" #include "locationvaluetypehelper_p.h" @@ -51,6 +55,10 @@ #include <QtPositioning/private/qdoublevector2d_p.h> #include <QtPositioning/private/qclipperutils_p.h> #include <QtPositioning/private/qgeopolygon_p.h> +#include <QtPositioning/private/qwebmercator_p.h> +#include <QtQuick/private/qsgmaterialshader_p.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/qsgnode.h> /* poly2tri triangulator includes */ #include <clip2tri.h> @@ -326,17 +334,307 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map, qreal stroke this->translate(QPointF(strokeWidth, strokeWidth)); } +QGeoMapPolygonGeometryOpenGL::QGeoMapPolygonGeometryOpenGL(){ +} + +void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QList<QDoubleVector2D> &path) +{ + QList<QGeoCoordinate> geopath; + for (const auto &c: path) + geopath.append(QWebMercator::mercatorToCoord(c)); + updateSourcePoints(map, geopath); +} + +// wrapPath always preserves the geometry +// This one handles holes +static void wrapPath(const QGeoPolygon &poly + ,const QGeoCoordinate &geoLeftBound + ,const QGeoProjectionWebMercator &p + ,QList<QList<QDoubleVector2D> > &wrappedPaths + ,QDoubleVector2D *leftBoundWrapped = nullptr) +{ + QList<QList<QDoubleVector2D> > paths; + for (int i = 0; i < 1+poly.holesCount(); ++i) { + QList<QDoubleVector2D> path; + if (!i) { + for (const QGeoCoordinate &c : poly.path()) + path << p.geoToMapProjection(c); + } else { + for (const QGeoCoordinate &c : poly.holePath(i-1)) + path << p.geoToMapProjection(c); + } + paths.append(path); + } + + const QDoubleVector2D leftBound = p.geoToMapProjection(geoLeftBound); + wrappedPaths.clear(); + + QList<QDoubleVector2D> wrappedPath; + // compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + for (int j = 0; j < paths.size(); ++j) { + const QList<QDoubleVector2D> &path = paths.at(j); + wrappedPath.clear(); + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D coord = path.at(i); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(coord.x()) || !qIsFinite(coord.y())) { + wrappedPaths.clear(); + return; + } + + const bool isPointLessThanUnwrapBelowX = (coord.x() < leftBound.x()); + // unwrap x to preserve geometry if moved to border of map + if (isPointLessThanUnwrapBelowX) + coord.setX(coord.x() + 1.0); + wrappedPath.append(coord); + } + wrappedPaths.append(wrappedPath); + } + + if (leftBoundWrapped) + *leftBoundWrapped = leftBound; +} + +static void cutPathEars(const QList<QList<QDoubleVector2D>> &wrappedPaths, + QVector<QDeclarativeGeoMapItemUtils::vec2> &screenVertices, + QVector<quint32> &screenIndices) +{ + using Coord = double; + using N = uint32_t; + using Point = std::array<Coord, 2>; + screenVertices.clear(); + screenIndices.clear(); + + std::vector<std::vector<Point>> polygon; + std::vector<Point> poly; + + for (const QList<QDoubleVector2D> &wrappedPath: wrappedPaths) { + poly.clear(); + for (const QDoubleVector2D &v: wrappedPath) { + screenVertices << v; + Point pt = {{ v.x(), v.y() }}; + poly.push_back( pt ); + } + polygon.push_back(poly); + } + + std::vector<N> indices = qt_mapbox::earcut<N>(polygon); + + for (const auto &i: indices) + screenIndices << quint32(i); +} + +static void cutPathEars(const QList<QDoubleVector2D> &wrappedPath, + QVector<QDeclarativeGeoMapItemUtils::vec2> &screenVertices, + QVector<quint32> &screenIndices) +{ + using Coord = double; + using N = uint32_t; + using Point = std::array<Coord, 2>; + screenVertices.clear(); + screenIndices.clear(); + + std::vector<std::vector<Point>> polygon; + std::vector<Point> poly; + + for (const QDoubleVector2D &v: wrappedPath) { + screenVertices << v; + Point pt = {{ v.x(), v.y() }}; + poly.push_back( pt ); + } + polygon.push_back(poly); + + std::vector<N> indices = qt_mapbox::earcut<N>(polygon); + + for (const auto &i: indices) + screenIndices << quint32(i); +} + +/*! + \internal +*/ +// This one does only a perimeter +void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, + const QList<QGeoCoordinate> &perimeter) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + // build the actual path + // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints + srcOrigin_ = geoLeftBound_; + + QDoubleVector2D leftBoundWrapped; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + QList<QDoubleVector2D> wrappedPath; + QDeclarativeGeoMapItemUtils::wrapPath(perimeter, geoLeftBound_, p, + wrappedPath, &leftBoundWrapped); + + // 1.1) do the same for the bbox + QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1; + QGeoPolygon bbox(QGeoPath(perimeter).boundingGeoRectangle()); + QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p, + wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped); + + // 2) Store the triangulated polygon, and the wrapped bbox paths. + // the triangulations can be used as they are, as they "bypass" the QtQuick display chain + // the bbox wraps have to be however clipped, and then projected, in order to figure out the geometry. + // Note that this might still cause the geometryChanged method to fail under some extreme conditions. + cutPathEars(wrappedPath, m_screenVertices, m_screenIndices); + + m_wrappedPolygons.resize(3); + m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1; + m_wrappedPolygons[1].wrappedBboxes = wrappedBbox; + m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1; +} + +// This one handles whole QGeoPolygon w. holes +void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + // build the actual path + // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints + srcOrigin_ = geoLeftBound_; + + QDoubleVector2D leftBoundWrapped; + QList<QList<QDoubleVector2D>> wrappedPath; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + wrapPath(poly, geoLeftBound_, p, + wrappedPath, &leftBoundWrapped); + + // 1.1) do the same for the bbox + QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1; + QGeoPolygon bbox(poly.boundingGeoRectangle()); + QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p, + wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped); + + // 2) Store the triangulated polygon, and the wrapped bbox paths. + // the triangulations can be used as they are, as they "bypass" the QtQuick display chain + // the bbox wraps have to be however clipped, and then projected, in order to figure out the geometry. + // Note that this might still cause the geometryChanged method to fail under some extreme conditions. + cutPathEars(wrappedPath, m_screenVertices, m_screenIndices); + m_wrappedPolygons.resize(3); + m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1; + m_wrappedPolygons[1].wrappedBboxes = wrappedBbox; + m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1; +} + +void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect) +{ + if (!sourceDirty_) + return; + const QList<QGeoCoordinate> perimeter = QDeclarativeRectangleMapItemPrivateCPU::path(rect); + updateSourcePoints(map, perimeter); +} + +/*! + \internal +*/ +void QGeoMapPolygonGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth , const QColor &strokeColor) +{ + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { + clear(); + return; + } + + // 1) identify which set to use: std, +1 or -1 + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(srcOrigin_); + m_wrapOffset = p.projectionWrapFactor(leftBoundMercator) + 1; // +1 to get the offset into QLists + + // 1.1) select geometry set + // This could theoretically be skipped for those polygons whose bbox is not even projectable. + // However, such optimization could only be introduced if not calculating bboxes lazily. + // Hence not doing it. + if (sourceDirty_) { + m_dataChanged = true; + } + + if (strokeWidth == 0.0 || strokeColor.alpha() == 0) // or else the geometry of the border is used, so no point in calculating 2 of them + updateQuickGeometry(p, strokeWidth); +} + +void QGeoMapPolygonGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal /*strokeWidth*/) +{ + // 2) clip bbox + // BBox handling -- this is related to the bounding box geometry + // that has to inevitably follow the old projection codepath + // As it needs to provide projected coordinates for QtQuick interaction. + // This could be futher optimized to be updated in a lazy fashion. + const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(m_wrapOffset).wrappedBboxes; + QList<QList<QDoubleVector2D> > clippedBbox; + QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped; + bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1)); + QDeclarativeGeoMapItemUtils::clipPolygon(wrappedBbox, p, clippedBbox, &bboxLeftBoundWrapped); + + // 3) project bbox + QPainterPath ppi; + if (!clippedBbox.size() || clippedBbox.first().size() < 3) { + sourceBounds_ = screenBounds_ = QRectF(); + firstPointOffset_ = QPointF(); + screenOutline_ = ppi; + return; + } + + QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox.first(), p, ppi); // Using first because a clipped box should always result in one polygon + const QRectF brect = ppi.boundingRect(); + firstPointOffset_ = QPointF(brect.topLeft()); + screenOutline_ = ppi; + + // 4) Set Screen bbox + screenBounds_ = brect; + sourceBounds_.setX(0); + sourceBounds_.setY(0); + sourceBounds_.setWidth(brect.width()); + sourceBounds_.setHeight(brect.height()); +} + +/* + * QDeclarativePolygonMapItem Private Implementations + */ + +QDeclarativePolygonMapItemPrivate::~QDeclarativePolygonMapItemPrivate() {} + +QDeclarativePolygonMapItemPrivateCPU::~QDeclarativePolygonMapItemPrivateCPU() {} + +QDeclarativePolygonMapItemPrivateOpenGL::~QDeclarativePolygonMapItemPrivateOpenGL() {} + +/* + * QDeclarativePolygonMapItem Implementation + */ + +struct PolygonBackendSelector +{ + PolygonBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolygonMapItem::OpenGL : QDeclarativePolygonMapItem::Software; + } + QDeclarativePolygonMapItem::Backend backend = QDeclarativePolygonMapItem::Software; +}; + +Q_GLOBAL_STATIC(PolygonBackendSelector, mapPolygonBackendSelector) + QDeclarativePolygonMapItem::QDeclarativePolygonMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), - updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), + m_updatingGeometry(false) + , m_d(new QDeclarativePolygonMapItemPrivateCPU(*this)) + { + // ToDo: handle envvar, and switch implementation. m_itemType = QGeoMap::MapPolygon; - geopath_ = QGeoPolygonEager(); + m_geopoly = QGeoPolygonEager(); setFlag(ItemHasContents, true); - QObject::connect(&border_, SIGNAL(colorChanged(QColor)), - this, SLOT(markSourceDirtyAndUpdate())); - QObject::connect(&border_, SIGNAL(widthChanged(qreal)), - this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&m_border, SIGNAL(colorChanged(QColor)), + this, SLOT(onLinePropertiesChanged())); // ToDo: fix this, only flag material? + QObject::connect(&m_border, SIGNAL(widthChanged(qreal)), + this, SLOT(onLinePropertiesChanged())); + setBackend(mapPolygonBackendSelector->backend); } QDeclarativePolygonMapItem::~QDeclarativePolygonMapItem() @@ -359,7 +657,44 @@ QDeclarativePolygonMapItem::~QDeclarativePolygonMapItem() QDeclarativeMapLineProperties *QDeclarativePolygonMapItem::border() { - return &border_; + return &m_border; +} + +/*! + \qmlproperty MapPolygon.Backend QtLocation::MapPolygon::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapPolygon.Software and \b{MapPolygon.OpenGL}. + The default value is \b{MapPolygon.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ +QDeclarativePolygonMapItem::Backend QDeclarativePolygonMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativePolygonMapItem::setBackend(QDeclarativePolygonMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativePolygonMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativePolygonMapItemPrivate *>(new QDeclarativePolygonMapItemPrivateCPU(*this)) + : static_cast<QDeclarativePolygonMapItemPrivate * >(new QDeclarativePolygonMapItemPrivateOpenGL(*this))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); } /*! @@ -368,12 +703,8 @@ QDeclarativeMapLineProperties *QDeclarativePolygonMapItem::border() void QDeclarativePolygonMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) { QDeclarativeGeoMapItemBase::setMap(quickMap,map); - if (map) { - regenerateCache(); - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); - } + if (map) + m_d->onMapSet(); } /*! @@ -387,7 +718,7 @@ void QDeclarativePolygonMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *m */ QJSValue QDeclarativePolygonMapItem::path() const { - return fromList(this, geopath_.path()); + return fromList(this, m_geopoly.path()); } void QDeclarativePolygonMapItem::setPath(const QJSValue &value) @@ -398,15 +729,12 @@ void QDeclarativePolygonMapItem::setPath(const QJSValue &value) QList<QGeoCoordinate> pathList = toList(this, value); // Equivalent to QDeclarativePolylineMapItem::setPathFromGeoList - if (geopath_.path() == pathList) + if (m_geopoly.path() == pathList) return; - geopath_.setPath(pathList); + m_geopoly.setPath(pathList); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -423,11 +751,8 @@ void QDeclarativePolygonMapItem::addCoordinate(const QGeoCoordinate &coordinate) if (!coordinate.isValid()) return; - geopath_.addCoordinate(coordinate); - updateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopoly.addCoordinate(coordinate); + m_d->onGeoGeometryUpdated(); emit pathChanged(); } @@ -443,15 +768,12 @@ void QDeclarativePolygonMapItem::addCoordinate(const QGeoCoordinate &coordinate) */ void QDeclarativePolygonMapItem::removeCoordinate(const QGeoCoordinate &coordinate) { - int length = geopath_.path().length(); - geopath_.removeCoordinate(coordinate); - if (geopath_.path().length() == length) + int length = m_geopoly.path().length(); + m_geopoly.removeCoordinate(coordinate); + if (m_geopoly.path().length() == length) return; - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -465,18 +787,18 @@ void QDeclarativePolygonMapItem::removeCoordinate(const QGeoCoordinate &coordina QColor QDeclarativePolygonMapItem::color() const { - return color_; + return m_color; } void QDeclarativePolygonMapItem::setColor(const QColor &color) { - if (color_ == color) + if (m_color == color) return; - color_ = color; - dirtyMaterial_ = true; - update(); - emit colorChanged(color_); + m_color = color; + m_dirtyMaterial = true; + polishAndUpdate(); // in case color was transparent and now is not or vice versa + emit colorChanged(m_color); } /*! @@ -484,22 +806,7 @@ void QDeclarativePolygonMapItem::setColor(const QColor &color) */ QSGNode *QDeclarativePolygonMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); - - if (!node) - node = new MapPolygonNode(); - - //TODO: update only material - if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { - node->update(color_, border_.color(), &geometry_, &borderGeometry_); - geometry_.setPreserveGeometry(false); - borderGeometry_.setPreserveGeometry(false); - geometry_.markClean(); - borderGeometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } /*! @@ -509,102 +816,34 @@ void QDeclarativePolygonMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - if (geopath_.path().length() == 0) { // Possibly cleared - geometry_.clear(); - borderGeometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - geometry_.updateSourcePoints(*map(), geopathProjected_); - geometry_.updateScreenPoints(*map(), border_.width()); - - QList<QGeoMapItemGeometry *> geoms; - geoms << &geometry_; - borderGeometry_.clear(); - - if (border_.color() != Qt::transparent && border_.width() > 0) { - QList<QDoubleVector2D> closedPath = geopathProjected_; - closedPath << closedPath.first(); - - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - - const QGeoCoordinate &geometryOrigin = geometry_.origin(); - - borderGeometry_.srcPoints_.clear(); - borderGeometry_.srcPointTypes_.clear(); - - QDoubleVector2D borderLeftBoundWrapped; - QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); - if (clippedPaths.size()) { - borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); - borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); - borderGeometry_.updateScreenPoints(*map(), border_.width()); - - geoms << &borderGeometry_; - } else { - borderGeometry_.clear(); - } - } - - QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); - setWidth(combined.width() + 2 * border_.width()); - setHeight(combined.height() + 2 * border_.width()); - - setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft() - + QPointF(border_.width(), border_.width())); + m_d->updatePolish(); } -void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate() +void QDeclarativePolygonMapItem::setMaterialDirty() { - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); + m_dirtyMaterial = true; + update(); } -/*! - \internal -*/ -void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate() { - if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) - return; - - geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); - borderGeometry_.setPreserveGeometry(true, borderGeometry_.geoLeftBound()); - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); } -/*! - \internal -*/ -void QDeclarativePolygonMapItem::regenerateCache() +void QDeclarativePolygonMapItem::onLinePropertiesChanged() { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) - return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_.clear(); - geopathProjected_.reserve(geopath_.path().size()); - for (const QGeoCoordinate &c : geopath_.path()) - geopathProjected_ << p.geoToMapProjection(c); + m_d->onLinePropertiesChanged(); } /*! \internal */ -void QDeclarativePolygonMapItem::updateCache() +void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + if (event.mapSize.isEmpty()) return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_ << p.geoToMapProjection(geopath_.path().last()); + + m_d->afterViewportChanged(); } /*! @@ -612,24 +851,21 @@ void QDeclarativePolygonMapItem::updateCache() */ bool QDeclarativePolygonMapItem::contains(const QPointF &point) const { - return (geometry_.contains(point) || borderGeometry_.contains(point)); + return m_d->contains(point); } const QGeoShape &QDeclarativePolygonMapItem::geoShape() const { - return geopath_; + return m_geopoly; } void QDeclarativePolygonMapItem::setGeoShape(const QGeoShape &shape) { - if (shape == geopath_) + if (shape == m_geopoly) return; - geopath_ = QGeoPolygonEager(shape); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopoly = QGeoPolygonEager(shape); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -638,7 +874,7 @@ void QDeclarativePolygonMapItem::setGeoShape(const QGeoShape &shape) */ void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !geopath_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopoly.isValid() || m_updatingGeometry) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } @@ -652,11 +888,8 @@ void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, cons if (offsetLati == 0.0 && offsetLongi == 0.0) return; - geopath_.translate(offsetLati, offsetLongi); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopoly.translate(offsetLati, offsetLongi); + m_d->onGeoGeometryChanged(); emit pathChanged(); // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested @@ -665,6 +898,25 @@ void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, cons ////////////////////////////////////////////////////////////////////// +QSGMaterialShader *MapPolygonMaterial::createShader() const +{ + return new MapPolygonShader(); +} + +int MapPolygonMaterial::compare(const QSGMaterial *other) const +{ + const MapPolygonMaterial &o = *static_cast<const MapPolygonMaterial *>(other); + if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset) + return QSGFlatColorMaterial::compare(other); + return -1; +} + +QSGMaterialType *MapPolygonMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + MapPolygonNode::MapPolygonNode() : border_(new MapPolylineNode()), geometry_(QSGGeometry::defaultAttributes_Point2D(), 0) @@ -701,6 +953,9 @@ void MapPolygonNode::update(const QColor &fillColor, const QColor &borderColor, setSubtreeBlocked(false); + // TODO: do this only if the geometry has changed!! + // No need to do this every frame. + // Then benchmark the difference! QSGGeometry *fill = QSGGeometryNode::geometry(); fillShape->allocateAndFill(fill); markDirty(DirtyGeometry); @@ -712,4 +967,90 @@ void MapPolygonNode::update(const QColor &fillColor, const QColor &borderColor, } } +MapPolygonNodeGL::MapPolygonNodeGL() : + //fill_material_(this), + fill_material_(), + geometry_(QSGGeometry::defaultAttributes_Point2D(), 0) +{ + geometry_.setDrawingMode(QSGGeometry::DrawTriangles); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); +} + +MapPolygonNodeGL::~MapPolygonNodeGL() +{ +} + +/*! + \internal +*/ +void MapPolygonNodeGL::update(const QColor &fillColor, + const QGeoMapPolygonGeometryOpenGL *fillShape, + const QMatrix4x4 &geoProjection, + const QDoubleVector3D ¢er) +{ + if (fillShape->m_screenIndices.size() < 3 || fillColor.alpha() == 0) { + setSubtreeBlocked(true); + return; + } + setSubtreeBlocked(false); + + QSGGeometry *fill = QSGGeometryNode::geometry(); + if (fillShape->m_dataChanged || !fill->vertexCount()) { + fillShape->allocateAndFillPolygon(fill); + markDirty(DirtyGeometry); + fillShape->m_dataChanged = false; + } + + //if (fillColor != fill_material_.color()) // Any point in optimizing this? + { + fill_material_.setColor(fillColor); + fill_material_.setGeoProjection(geoProjection); + fill_material_.setCenter(center); + fill_material_.setWrapOffset(fillShape->m_wrapOffset - 1); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +MapPolygonShader::MapPolygonShader() : QSGMaterialShader(*new QSGMaterialShaderPrivate) +{ + +} + +void MapPolygonShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); + MapPolygonMaterial *oldMaterial = static_cast<MapPolygonMaterial *>(oldEffect); + MapPolygonMaterial *newMaterial = static_cast<MapPolygonMaterial *>(newEffect); + + const QColor &c = newMaterial->color(); + const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); + const QDoubleVector3D ¢er = newMaterial->center(); + + QVector3D vecCenter, vecCenter_lowpart; + for (int i = 0; i < 3; i++) + QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]); + + if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { + float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + program()->setUniformValue(m_color_id, v); + } + + if (state.isMatrixDirty()) + { + program()->setUniformValue(m_matrix_id, state.projectionMatrix()); + } + + program()->setUniformValue(m_mapProjection_id, geoProjection); + + program()->setUniformValue(m_center_id, vecCenter); + program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart); + program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset())); +} + QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h b/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h index 810a8d35..efc1a137 100644 --- a/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h @@ -51,44 +51,29 @@ #include <QtLocation/private/qlocationglobal_p.h> #include <QtLocation/private/qdeclarativegeomapitembase_p.h> #include <QtLocation/private/qdeclarativepolylinemapitem_p.h> -#include <QtLocation/private/qgeomapitemgeometry_p.h> #include <QtPositioning/qgeopolygon.h> -#include <QSGGeometryNode> -#include <QSGFlatColorMaterial> - QT_BEGIN_NAMESPACE -class MapPolygonNode; - -class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolygonGeometry : public QGeoMapItemGeometry -{ -public: - QGeoMapPolygonGeometry(); - - inline void setAssumeSimple(bool value) { assumeSimple_ = value; } - - void updateSourcePoints(const QGeoMap &map, - const QList<QDoubleVector2D> &path); - - void updateScreenPoints(const QGeoMap &map, qreal strokeWidth = 0.0); - -protected: - QPainterPath srcPath_; - bool assumeSimple_; -}; - +class QDeclarativePolygonMapItemPrivate; class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolygonMapItem : public QDeclarativeGeoMapItemBase { Q_OBJECT + Q_ENUMS(Backend) Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + Q_PROPERTY(Backend backend READ backend WRITE setBackend NOTIFY backendChanged REVISION 15) public: - explicit QDeclarativePolygonMapItem(QQuickItem *parent = 0); - ~QDeclarativePolygonMapItem(); + enum Backend { + Software = 0, + OpenGL = 1 + }; + + explicit QDeclarativePolygonMapItem(QQuickItem *parent = nullptr); + ~QDeclarativePolygonMapItem() override; virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) override; //from QuickItem @@ -105,6 +90,9 @@ public: QDeclarativeMapLineProperties *border(); + Backend backend() const; + void setBackend(Backend b); + bool contains(const QPointF &point) const override; const QGeoShape &geoShape() const override; void setGeoShape(const QGeoShape &shape) override; @@ -112,45 +100,34 @@ public: Q_SIGNALS: void pathChanged(); void colorChanged(const QColor &color); - -protected: - void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; - void updatePolish() override; + void backendChanged(); protected Q_SLOTS: void markSourceDirtyAndUpdate(); + void onLinePropertiesChanged(); virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) override; -private: - void regenerateCache(); - void updateCache(); - - QGeoPolygon geopath_; - QList<QDoubleVector2D> geopathProjected_; - QDeclarativeMapLineProperties border_; - QColor color_; - bool dirtyMaterial_; - QGeoMapPolygonGeometry geometry_; - QGeoMapPolylineGeometry borderGeometry_; - bool updatingGeometry_; -}; - -////////////////////////////////////////////////////////////////////// - -class Q_LOCATION_PRIVATE_EXPORT MapPolygonNode : public MapItemGeometryNode -{ +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void updatePolish() override; + void setMaterialDirty() override; +#ifdef QT_LOCATION_DEBUG 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_; +#endif + QGeoPolygon m_geopoly; + QDeclarativeMapLineProperties m_border; + QColor m_color; + Backend m_backend = Software; + bool m_dirtyMaterial; +// bool m_dirtyGeometry = false; + bool m_updatingGeometry; + + QScopedPointer<QDeclarativePolygonMapItemPrivate> m_d; + + friend class QDeclarativePolygonMapItemPrivate; + friend class QDeclarativePolygonMapItemPrivateCPU; + friend class QDeclarativePolygonMapItemPrivateOpenGL; }; QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h b/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h new file mode 100644 index 00000000..83f1100e --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h @@ -0,0 +1,664 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qgeomapitemgeometry_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qdeclarativegeomapitemutils_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p_p.h> +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> +#include <QtPositioning/QGeoPath> +#include <QtPositioning/QGeoRectangle> +#include <QtPositioning/QGeoPolygon> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QSGFlatColorMaterial> +#include <QSGSimpleMaterial> +#include <QtGui/QMatrix4x4> +#include <QColor> +#include <QList> +#include <QVector> +#include <QtCore/QScopedValueRollback> + +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<QDoubleVector2D> &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<QDoubleVector2D> wrappedBboxes; + } WrappedPolygon; + QGeoMapPolygonGeometryOpenGL(); + ~QGeoMapPolygonGeometryOpenGL() override {} + + // Temporary method for compatibility in MapCircleObject. Remove when MapObjects are ported. + void updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path); + + void updateSourcePoints(const QGeoMap &map, + const QList<QGeoCoordinate> &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<QDeclarativeGeoMapItemUtils::vec2> &vx = m_screenVertices; + const QVector<quint32> &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<QDeclarativeGeoMapItemUtils::vec2> m_screenVertices; + QVector<quint32> m_screenIndices; + QDoubleVector2D m_bboxLeftBoundWrapped; + QVector<WrappedPolygon> 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<const QGeoProjectionWebMercator&>(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<const QGeoProjectionWebMercator&>(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<const QGeoProjectionWebMercator&>(map->geoProjection()); + QScopedValueRollback<bool> rollback(m_poly.m_updatingGeometry); + m_poly.m_updatingGeometry = true; + + m_geometry.updateSourcePoints(*map, m_geopathProjected); + m_geometry.updateScreenPoints(*map, borderWidth); + + QList<QGeoMapItemGeometry *> geoms; + geoms << &m_geometry; + m_borderGeometry.clear(); + + if (m_poly.m_border.color().alpha() != 0 && borderWidth > 0) { + QList<QDoubleVector2D> 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<QList<QDoubleVector2D > > 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<MapPolygonNode *>(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<QDoubleVector2D> 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<bool> 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<RootNode *>(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); + 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<const QGeoProjectionWebMercator&>(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 diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp index 10f3f0c3..7e484122 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp @@ -35,11 +35,16 @@ ****************************************************************************/ #include "qdeclarativepolylinemapitem_p.h" +#include "qdeclarativepolylinemapitem_p_p.h" +#include "qdeclarativerectanglemapitem_p_p.h" +#include "qdeclarativecirclemapitem_p_p.h" #include "qlocationutils_p.h" +#include "qdeclarativegeomapitemutils_p.h" #include "error_messages_p.h" #include "locationvaluetypehelper_p.h" #include "qdoublevector2d_p.h" #include <QtLocation/private/qgeomap_p.h> +#include <QtPositioning/private/qwebmercator_p.h> #include <QtCore/QScopedValueRollback> #include <QtQml/QQmlInfo> @@ -55,7 +60,9 @@ #include <QtPositioning/private/qclipperutils_p.h> #include <QtPositioning/private/qgeopath_p.h> +#include <QtQuick/private/qsgmaterialshader_p.h> #include <array> +#include <QtLocation/private/qgeomapparameter_p.h> QT_BEGIN_NAMESPACE @@ -125,7 +132,7 @@ static QList<QList<QDoubleVector2D> > clipLine( const QList<QDoubleVector2D> &poly) { QList<QList<QDoubleVector2D> > res; - if (poly.size() < 3 || l.size() < 2) + if (poly.size() < 2 || l.size() < 2) return res; // Step 1: build edges @@ -355,11 +362,6 @@ void QDeclarativeMapLineProperties::setWidth(qreal width) emit widthChanged(width_); } -struct Vertex -{ - QVector2D position; -}; - QGeoMapPolylineGeometry::QGeoMapPolylineGeometry() { } @@ -661,7 +663,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, // Create the viewport rect in the same coordinate system // as the actual points QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); - viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth); + viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth * 2, strokeWidth * 2); viewport.translate(-1 * origin); QVector<qreal> points; @@ -719,7 +721,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, } screenBounds_ = bb; - const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) : QPointF(); + const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) * 0.5: QPointF(); this->translate( -1 * sourceBounds_.topLeft() + strokeOffset); } @@ -747,16 +749,199 @@ bool QGeoMapPolylineGeometry::contains(const QPointF &point) const return false; } +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly) +{ + if (!sourceDirty_) + return; + QGeoPath p(poly.path()); + if (poly.path().size() && poly.path().last() != poly.path().first()) + p.addCoordinate(poly.path().first()); + updateSourcePoints(map, p); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPath &poly) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + // build the actual path + // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints + + + QDoubleVector2D leftBoundWrapped; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + QList<QDoubleVector2D> wrappedPath; + QDeclarativeGeoMapItemUtils::wrapPath(poly.path(), geoLeftBound_, p, + wrappedPath, &leftBoundWrapped); + const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle(); + updateSourcePoints(p, wrappedPath, boundingRectangle); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMercator &p, + const QList<QDoubleVector2D> &wrappedPath, + const QGeoRectangle &boundingRectangle) { + if (!sourceDirty_) + return; + // 1.1) do the same for the bbox + // Beware: vertical lines (or horizontal lines) might have an "empty" bbox. Check for that + + QGeoCoordinate topLeft = boundingRectangle.topLeft(); + QGeoCoordinate bottomRight = boundingRectangle.bottomRight(); + const qreal epsilon = 0.000001; + if (qFuzzyCompare(topLeft.latitude(), bottomRight.latitude())) { + topLeft.setLatitude(qBound(-90.0, topLeft.latitude() + epsilon ,90.0)); + bottomRight.setLatitude(qBound(-90.0, bottomRight.latitude() - epsilon ,90.0)); + } + if (qFuzzyCompare(topLeft.longitude(), bottomRight.longitude())) { + topLeft.setLongitude(QLocationUtils::wrapLong(topLeft.longitude() - epsilon)); + bottomRight.setLongitude(QLocationUtils::wrapLong(bottomRight.longitude() + epsilon)); + } + QGeoPolygon bbox(QGeoRectangle(topLeft, bottomRight)); + QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1; + QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p, + wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped); + + m_screenVertices.clear(); + for (const auto &v: qAsConst(wrappedPath)) m_screenVertices << v; + + m_wrappedPolygons.resize(3); + m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1; + m_wrappedPolygons[1].wrappedBboxes = wrappedBbox; + m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1; + srcOrigin_ = geoLeftBound_; +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect) +{ + const QGeoPath path(QDeclarativeRectangleMapItemPrivateCPU::perimeter(rect)); + updateSourcePoints(map, path); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoCircle &circle) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + QDoubleVector2D leftBoundWrapped; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + QList<QGeoCoordinate> path; + QGeoCoordinate leftBound; + QList<QDoubleVector2D> wrappedPath; + QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, circle.center(), circle.radius(), QDeclarativeCircleMapItemPrivateCPU::CircleSamples, leftBound); + path << path.first(); + geoLeftBound_ = leftBound; + QDeclarativeGeoMapItemUtils::wrapPath(path, leftBound, p, wrappedPath, &leftBoundWrapped); + const QGeoRectangle &boundingRectangle = circle.boundingGeoRectangle(); + updateSourcePoints(p, wrappedPath, boundingRectangle); +} + +void QGeoMapPolylineGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth, bool /*adjustTranslation*/) +{ + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { + clear(); + return; + } + + // 1) identify which set to use: std, +1 or -1 + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(srcOrigin_); + m_wrapOffset = p.projectionWrapFactor(leftBoundMercator) + 1; // +1 to get the offset into QLists + + if (sourceDirty_) { + // 1.1) select geometry set + // This could theoretically be skipped for those polylines whose bbox is not even projectable. + // However, such optimization could only be introduced if not calculating bboxes lazily. + // Hence not doing it. +// if (m_screenVertices.size() > 1) + m_dataChanged = true; + } + + updateQuickGeometry(p, strokeWidth); +} + +void QGeoMapPolylineGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth) +{ + // 2) clip bbox + // BBox handling -- this is related to the bounding box geometry + // that has to inevitably follow the old projection codepath + // As it needs to provide projected coordinates for QtQuick interaction. + // This could be futher optimized to be updated in a lazy fashion. + const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(m_wrapOffset).wrappedBboxes; + QList<QList<QDoubleVector2D> > clippedBbox; + QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped; + bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1)); + QDeclarativeGeoMapItemUtils::clipPolygon(wrappedBbox, p, clippedBbox, &bboxLeftBoundWrapped, false); + + // 3) project bbox + QPainterPath ppi; + + if ( !clippedBbox.size() || + clippedBbox.first().size() < 3) { + sourceBounds_ = screenBounds_ = QRectF(); + firstPointOffset_ = QPointF(); + screenOutline_ = ppi; + return; + } + + QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox.first(), p, ppi); // Using first because a clipped box should always result in one polygon + const QRectF brect = ppi.boundingRect(); + firstPointOffset_ = QPointF(brect.topLeft()); + sourceBounds_ = brect; + screenOutline_ = ppi; + + // 4) Set Screen bbox + screenBounds_ = brect; + sourceBounds_.setX(0); + sourceBounds_.setY(0); + sourceBounds_.setWidth(brect.width() + strokeWidth); + sourceBounds_.setHeight(brect.height() + strokeWidth); +} + +/* + * QDeclarativePolygonMapItem Private Implementations + */ + +QDeclarativePolylineMapItemPrivate::~QDeclarativePolylineMapItemPrivate() {} + + +QDeclarativePolylineMapItemPrivateCPU::~QDeclarativePolylineMapItemPrivateCPU() {} + +QDeclarativePolylineMapItemPrivateOpenGLLineStrip::~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() {} + +QDeclarativePolylineMapItemPrivateOpenGLExtruded::~QDeclarativePolylineMapItemPrivateOpenGLExtruded() {} + +/* + * QDeclarativePolygonMapItem Implementation + */ + +struct PolylineBackendSelector +{ + PolylineBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolylineMapItem::OpenGLExtruded : QDeclarativePolylineMapItem::Software; + } + QDeclarativePolylineMapItem::Backend backend = QDeclarativePolylineMapItem::Software; +}; + +Q_GLOBAL_STATIC(PolylineBackendSelector, mapPolylineBackendSelector) + QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), line_(this), dirtyMaterial_(true), updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), + m_line(this), + m_dirtyMaterial(true), + m_updatingGeometry(false), + m_d(new QDeclarativePolylineMapItemPrivateCPU(*this)) { m_itemType = QGeoMap::MapPolyline; - geopath_ = QGeoPathEager(); + m_geopath = QGeoPathEager(); setFlag(ItemHasContents, true); - QObject::connect(&line_, SIGNAL(colorChanged(QColor)), + QObject::connect(&m_line, SIGNAL(colorChanged(QColor)), this, SLOT(updateAfterLinePropertiesChanged())); - QObject::connect(&line_, SIGNAL(widthChanged(qreal)), + QObject::connect(&m_line, SIGNAL(widthChanged(qreal)), this, SLOT(updateAfterLinePropertiesChanged())); + setBackend(mapPolylineBackendSelector->backend); } QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() @@ -768,9 +953,7 @@ QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() */ void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() { - // mark dirty just in case we're a width change - geometry_.markSourceDirty(); - polishAndUpdate(); + m_d->onLinePropertiesChanged(); } /*! @@ -779,11 +962,8 @@ void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) { QDeclarativeGeoMapItemBase::setMap(quickMap,map); - if (map) { - regenerateCache(); - geometry_.markSourceDirty(); - polishAndUpdate(); - } + if (map) + m_d->onMapSet(); } /*! @@ -795,7 +975,7 @@ void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap * QJSValue QDeclarativePolylineMapItem::path() const { - return fromList(this, geopath_.path()); + return fromList(this, m_geopath.path()); } void QDeclarativePolylineMapItem::setPath(const QJSValue &value) @@ -817,13 +997,11 @@ void QDeclarativePolylineMapItem::setPath(const QJSValue &value) */ void QDeclarativePolylineMapItem::setPath(const QGeoPath &path) { - if (geopath_.path() == path.path()) + if (m_geopath.path() == path.path()) return; - geopath_ = QGeoPathEager(path); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopath = QGeoPathEager(path); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -832,14 +1010,12 @@ void QDeclarativePolylineMapItem::setPath(const QGeoPath &path) */ void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path) { - if (geopath_.path() == path) + if (m_geopath.path() == path) return; - geopath_.setPath(path); + m_geopath.setPath(path); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -854,7 +1030,7 @@ void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> */ int QDeclarativePolylineMapItem::pathLength() const { - return geopath_.path().length(); + return m_geopath.path().length(); } /*! @@ -869,11 +1045,9 @@ void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate if (!coordinate.isValid()) return; - geopath_.addCoordinate(coordinate); + m_geopath.addCoordinate(coordinate); - updateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryUpdated(); emit pathChanged(); } @@ -888,14 +1062,12 @@ void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate */ void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate) { - if (index < 0 || index > geopath_.path().length()) + if (index < 0 || index > m_geopath.path().length()) return; - geopath_.insertCoordinate(index, coordinate); + m_geopath.insertCoordinate(index, coordinate); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -911,14 +1083,12 @@ void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordina */ void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate) { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return; - geopath_.replaceCoordinate(index, coordinate); + m_geopath.replaceCoordinate(index, coordinate); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -933,10 +1103,10 @@ void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordin */ QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return QGeoCoordinate(); - return geopath_.coordinateAt(index); + return m_geopath.coordinateAt(index); } /*! @@ -948,7 +1118,7 @@ QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const */ bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate) { - return geopath_.containsCoordinate(coordinate); + return m_geopath.containsCoordinate(coordinate); } /*! @@ -963,13 +1133,12 @@ bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coord */ void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate) { - int length = geopath_.path().length(); - geopath_.removeCoordinate(coordinate); - if (geopath_.path().length() == length) + int length = m_geopath.path().length(); + m_geopath.removeCoordinate(coordinate); + if (m_geopath.path().length() == length) return; - regenerateCache(); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -986,14 +1155,12 @@ void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordin */ void QDeclarativePolylineMapItem::removeCoordinate(int index) { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return; - geopath_.removeCoordinate(index); + m_geopath.removeCoordinate(index); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -1013,7 +1180,47 @@ void QDeclarativePolylineMapItem::removeCoordinate(int index) QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() { - return &line_; + return &m_line; +} + +/*! + \qmlproperty MapPolyline.Backend QtLocation::MapPolyline::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapPolyline.Software and \b{MapPolyline.OpenGLLineStrip} + and \b{MapPolyline.OpenGLExtruded}. + The default value is \b{MapPolyline.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ +QDeclarativePolylineMapItem::Backend QDeclarativePolylineMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativePolylineMapItem::setBackend(QDeclarativePolylineMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativePolylineMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativePolylineMapItemPrivate *>(new QDeclarativePolylineMapItemPrivateCPU(*this)) + : ((m_backend == OpenGLExtruded) + ? static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLExtruded(*this)) + : static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLLineStrip(*this)))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); } /*! @@ -1021,7 +1228,7 @@ QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() */ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !geopath_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopath.isValid() || m_updatingGeometry) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } @@ -1035,10 +1242,8 @@ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, con if (offsetLati == 0.0 && offsetLongi == 0.0) return; - geopath_.translate(offsetLati, offsetLongi); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopath.translate(offsetLati, offsetLongi); + m_d->onGeoGeometryChanged(); emit pathChanged(); // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested @@ -1050,68 +1255,78 @@ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, con */ void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) { - if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + if (event.mapSize.isEmpty()) return; - geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); - markSourceDirtyAndUpdate(); + m_d->afterViewportChanged(); } /*! \internal */ -void QDeclarativePolylineMapItem::regenerateCache() +void QDeclarativePolylineMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_.clear(); - geopathProjected_.reserve(geopath_.path().size()); - for (const QGeoCoordinate &c : geopath_.path()) - geopathProjected_ << p.geoToMapProjection(c); + m_d->updatePolish(); } -/*! - \internal -*/ -void QDeclarativePolylineMapItem::updateCache() +void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, + const char *propertyName, + bool update) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) - return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_ << p.geoToMapProjection(geopath_.path().last()); + static const QByteArrayList acceptedParameterTypes = QByteArrayList() + << QByteArrayLiteral("lineCap") + << QByteArrayLiteral("pen"); + switch (acceptedParameterTypes.indexOf(QByteArray(propertyName))) { + case -1: + qWarning() << "Invalid property " << QLatin1String(propertyName) << " for parameter lineStyle"; + break; + case 0: // lineCap + { + const QVariant lineCap = p->property("lineCap"); + m_d->m_penCapStyle = lineCap.value<Qt::PenCapStyle>(); // if invalid, will return 0 == FlatCap + if (update) + markSourceDirtyAndUpdate(); + break; + } + case 1: // penStyle + { + const QVariant penStyle = p->property("pen"); + m_d->m_penStyle = penStyle.value<Qt::PenStyle>(); + if (m_d->m_penStyle == Qt::NoPen) + m_d->m_penStyle = Qt::SolidLine; + if (update) + markSourceDirtyAndUpdate(); + break; + } + } } -/*! - \internal -*/ -void QDeclarativePolylineMapItem::updatePolish() +void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) - return; - if (geopath_.path().length() == 0) { // Possibly cleared - geometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - geometry_.updateSourcePoints(*map(), geopathProjected_, geopath_.boundingGeoRectangle().topLeft()); - geometry_.updateScreenPoints(*map(), line_.width()); - - setWidth(geometry_.sourceBoundingBox().width() + 2 * line_.width()); - setHeight(geometry_.sourceBoundingBox().height() + 2 * line_.width()); + updateLineStyleParameter(p, propertyName, true); +} - setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft() + QPointF(line_.width(), line_.width())); +void QDeclarativePolylineMapItem::componentComplete() +{ + QQuickItem::componentComplete(); + // Set up Dynamic Parameters + QList<QGeoMapParameter *> dynamicParameters = quickChildren<QGeoMapParameter>(); + for (QGeoMapParameter *p : qAsConst(dynamicParameters)) { + if (p->type() == QLatin1String("lineStyle")) { + updateLineStyleParameter(p, "lineCap", false); + updateLineStyleParameter(p, "pen", false); + connect(p, &QGeoMapParameter::propertyUpdated, + this, static_cast<void (QDeclarativePolylineMapItem::*)(QGeoMapParameter *, const char *)>(&QDeclarativePolylineMapItem::updateLineStyleParameter)); + markSourceDirtyAndUpdate(); + } + } } void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() { - geometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); } /*! @@ -1119,32 +1334,17 @@ void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() */ QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - - MapPolylineNode *node = static_cast<MapPolylineNode *>(oldNode); - - if (!node) { - node = new MapPolylineNode(); - } - - //TODO: update only material - if (geometry_.isScreenDirty() || dirtyMaterial_ || !oldNode) { - node->update(line_.color(), &geometry_); - geometry_.setPreserveGeometry(false); - geometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } bool QDeclarativePolylineMapItem::contains(const QPointF &point) const { - return geometry_.contains(point); + return m_d->contains(point); } const QGeoShape &QDeclarativePolylineMapItem::geoShape() const { - return geopath_; + return m_geopath; } void QDeclarativePolylineMapItem::setGeoShape(const QGeoShape &shape) @@ -1254,4 +1454,425 @@ void MapPolylineNode::update(const QColor &fillColor, } } +MapPolylineNodeOpenGLLineStrip::MapPolylineNodeOpenGLLineStrip() +: geometry_(QSGGeometry::defaultAttributes_Point2D(), 0) +{ + geometry_.setDrawingMode(QSGGeometry::DrawLineStrip); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); +} + +MapPolylineNodeOpenGLLineStrip::~MapPolylineNodeOpenGLLineStrip() +{ + +} + +void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor, + const qreal lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 &geoProjection, + const QDoubleVector3D ¢er, + const Qt::PenCapStyle /*capStyle*/) +{ + if (shape->m_screenVertices.size() < 2) { + setSubtreeBlocked(true); + return; + } else { + setSubtreeBlocked(false); + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + if (shape->m_dataChanged) { + shape->allocateAndFillLineStrip(fill); + markDirty(DirtyGeometry); + shape->m_dataChanged = false; + } + fill->setLineWidth(lineWidth); + fill_material_.setLineWidth(lineWidth); // to make the material not compare equal if linewidth changes + +// if (fillColor != fill_material_.color()) + { + fill_material_.setWrapOffset(shape->m_wrapOffset - 1); + fill_material_.setColor(fillColor); + fill_material_.setGeoProjection(geoProjection); + fill_material_.setCenter(center); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +MapPolylineShaderLineStrip::MapPolylineShaderLineStrip() : QSGMaterialShader(*new QSGMaterialShaderPrivate) +{ + +} + +void MapPolylineShaderLineStrip::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); + MapPolylineMaterial *oldMaterial = static_cast<MapPolylineMaterial *>(oldEffect); + MapPolylineMaterial *newMaterial = static_cast<MapPolylineMaterial *>(newEffect); + + const QColor &c = newMaterial->color(); + const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); + const QDoubleVector3D ¢er = newMaterial->center(); + + QVector3D vecCenter, vecCenter_lowpart; + for (int i = 0; i < 3; i++) + QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]); + + if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { + float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + program()->setUniformValue(m_color_id, v); + } + + if (state.isMatrixDirty()) + { + program()->setUniformValue(m_matrix_id, state.projectionMatrix()); + } + + program()->setUniformValue(m_mapProjection_id, geoProjection); + + program()->setUniformValue(m_center_id, vecCenter); + program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart); + program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset())); +} + +const char * const *MapPolylineShaderLineStrip::attributeNames() const +{ + static char const *const attr[] = { "vertex", nullptr }; + return attr; +} + +QSGMaterialShader *MapPolylineMaterial::createShader() const +{ + return new MapPolylineShaderLineStrip(); +} + +QSGMaterialType *MapPolylineMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +int MapPolylineMaterial::compare(const QSGMaterial *other) const +{ + const MapPolylineMaterial &o = *static_cast<const MapPolylineMaterial *>(other); + if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset && o.m_lineWidth == m_lineWidth) + return QSGFlatColorMaterial::compare(other); + return -1; +} + +const QSGGeometry::AttributeSet &MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated() +{ + return MapPolylineEntry::attributes(); +} + +MapPolylineNodeOpenGLExtruded::MapPolylineNodeOpenGLExtruded() +: m_geometryTriangulating(MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated(), + 0 /* vtx cnt */, 0 /* index cnt */, QSGGeometry::UnsignedIntType /* index type */) +{ + m_geometryTriangulating.setDrawingMode(QSGGeometry::DrawTriangles); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&m_geometryTriangulating); +} + +MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded() +{ + +} + +void QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, bool closed) const +{ + // ToDo: add dirty flag. + const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = m_screenVertices; + if (v.size() < 2) { + geom->allocate(0, 0); + return; + } + const int numSegments = (v.size() - 1); + + const int numIndices = numSegments * 6; // six vertices per line segment + geom->allocate(numIndices); + MapPolylineNodeOpenGLExtruded::MapPolylineEntry *vertices = + static_cast<MapPolylineNodeOpenGLExtruded::MapPolylineEntry *>(geom->vertexData()); + + for (int i = 0; i < numSegments; ++i) { + MapPolylineNodeOpenGLExtruded::MapPolylineEntry e; + const QDeclarativeGeoMapItemUtils::vec2 &cur = v[i]; + const QDeclarativeGeoMapItemUtils::vec2 &next = v[i+1]; + e.triangletype = 1.0; + e.next = next; + e.prev = cur; + e.pos = cur; + e.direction = 1.0; + e.vertextype = -1.0; + vertices[i*6] = e; + e.direction = -1.0; + vertices[i*6+1] = e; + e.pos = next; + e.vertextype = 1.0; + vertices[i*6+2] = e; + + // Second tri + e.triangletype = -1.0; + e.direction = -1.0; + vertices[i*6+3] = e; + e.direction = 1.0; + vertices[i*6+4] = e; + e.pos = cur; + e.vertextype = -1.0; + vertices[i*6+5] = e; + + if (i != 0) { + vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[i-1]; + } else { + if (closed) { + vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[numSegments - 1]; + } else { + vertices[i*6].triangletype = vertices[i*6+1].triangletype = vertices[i*6+5].triangletype = 2.0; + } + } + if (i != numSegments - 1) { + vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[i+2]; + } else { + if (closed) { + vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[1]; + } else { + vertices[i*6+2].triangletype = vertices[i*6+3].triangletype = vertices[i*6+4].triangletype = 3.0; + } + } + } +} + +void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom) const +{ + const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = m_screenVertices; + geom->allocate(vx.size()); + + QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); + for (int i = 0; i < vx.size(); ++i) + pts[i].set(vx[i].x, vx[i].y); +} + +void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor, + const float lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 geoProjection, + const QDoubleVector3D center, + const Qt::PenCapStyle capStyle, + bool closed) +{ + // shape->size() == number of triangles + if (shape->m_screenVertices.size() < 2 + || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points + setSubtreeBlocked(true); + return; + } else { + setSubtreeBlocked(false); + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + if (shape->m_dataChanged || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated. + shape->allocateAndFillEntries(fill, closed); + markDirty(DirtyGeometry); + shape->m_dataChanged = false; + } + + // Update this +// if (fillColor != fill_material_.color()) + { + fill_material_.setWrapOffset(shape->m_wrapOffset - 1); + fill_material_.setColor(fillColor); + fill_material_.setGeoProjection(geoProjection); + fill_material_.setCenter(center); + fill_material_.setLineWidth(lineWidth); + fill_material_.setMiter(capStyle != Qt::FlatCap); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +MapPolylineShaderExtruded::MapPolylineShaderExtruded() : QSGMaterialShader(*new QSGMaterialShaderPrivate) +{ + +} + +void MapPolylineShaderExtruded::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); + MapPolylineMaterialExtruded *oldMaterial = static_cast<MapPolylineMaterialExtruded *>(oldEffect); + MapPolylineMaterialExtruded *newMaterial = static_cast<MapPolylineMaterialExtruded *>(newEffect); + + const QColor &c = newMaterial->color(); + const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); + const QDoubleVector3D ¢er = newMaterial->center(); + + QVector3D vecCenter, vecCenter_lowpart; + for (int i = 0; i < 3; i++) + QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]); + + if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { + float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + program()->setUniformValue(m_color_id, v); + } + + if (state.isMatrixDirty()) + { + program()->setUniformValue(m_matrix_id, state.projectionMatrix()); + } + + // ToDo: dirty-flag all this + program()->setUniformValue(m_mapProjection_id, geoProjection); + + program()->setUniformValue(m_center_id, vecCenter); + program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart); + program()->setUniformValue(m_miter_id, newMaterial->miter()); + program()->setUniformValue(m_lineWidth_id, newMaterial->lineWidth()); + program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset())); + + const QRectF viewportRect = state.viewportRect(); + const float aspect = float(viewportRect.width() / viewportRect.height()); + program()->setUniformValue(m_aspect_id, aspect); +} + +const char * const *MapPolylineShaderExtruded::attributeNames() const +{ + return MapPolylineNodeOpenGLExtruded::MapPolylineEntry::attributeNames(); +} + +QSGMaterialShader *MapPolylineMaterialExtruded::createShader() const +{ + return new MapPolylineShaderExtruded(); +} + +QSGMaterialType *MapPolylineMaterialExtruded::type() const +{ + static QSGMaterialType type; + return &type; +} + +int MapPolylineMaterialExtruded::compare(const QSGMaterial *other) const +{ + const MapPolylineMaterialExtruded &o = *static_cast<const MapPolylineMaterialExtruded *>(other); + if (o.m_miter == m_miter) + return MapPolylineMaterial::compare(other); + return -1; +} + +const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const +{ + return + "attribute highp vec4 vertex;\n" + "attribute highp vec4 previous;\n" + "attribute highp vec4 next;\n" + "attribute lowp float direction;\n" + "attribute lowp float triangletype;\n" + "attribute lowp float vertextype;\n" // -1.0 if it is the "left" end of the segment, 1.0 if it is the "right" end. + "\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 lineWidth;\n" + "uniform lowp float aspect;\n" + "uniform lowp int miter;\n" // currently unused + "uniform lowp vec4 color;\n" + "uniform lowp float wrapOffset;\n" + "\n" + "varying vec4 primitivecolor;\n" + "\n" + " \n" + "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n" + "void main() {\n" // ln 22 + " primitivecolor = color;\n" + " vec2 aspectVec = vec2(aspect, 1.0);\n" + " mat4 projViewModel = qt_Matrix * mapProjection;\n" + " vec4 cur = wrapped(vertex) - vec4(center, 0.0);\n" + " cur = cur - vec4(center_lowpart, 0.0);\n" + " vec4 prev = wrapped(previous) - vec4(center, 0.0);\n" + " prev = prev - vec4(center_lowpart, 0.0);\n" + " vec4 nex = wrapped(next) - vec4(center, 0.0);\n" + " nex = nex - vec4(center_lowpart, 0.0);\n" + "\n" + " vec4 centerProjected = projViewModel * vec4(center, 1.0);\n" + " vec4 previousProjected = projViewModel * prev;\n" + " vec4 currentProjected = projViewModel * cur;\n" + " vec4 nextProjected = projViewModel * nex;\n" + "\n" + " //get 2D screen space with W divide and aspect correction\n" + " vec2 currentScreen = (currentProjected.xy / currentProjected.w) * aspectVec;\n" + " vec2 previousScreen = (previousProjected.xy / previousProjected.w) * aspectVec;\n" + " vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n" + " float len = (lineWidth);\n" + " float orientation = direction;\n" + " bool clipped = false;\n" + " bool otherEndBelowFrustum = false;\n" + " //starting point uses (next - current)\n" + " vec2 dir = vec2(0.0);\n" + " if (vertextype < 0.0) {\n" + " dir = normalize(nextScreen - currentScreen);\n" + " if (nextProjected.z < 0.0) dir = -dir;\n" + " } else { \n" + " dir = normalize(currentScreen - previousScreen);\n" + " if (previousProjected.z < 0.0) dir = -dir;\n" + " }\n" + // first, clip current, and make sure currentProjected.z is > 0 + " if (currentProjected.z < 0.0) {\n" + " if ((nextProjected.z > 0.0 && vertextype < 0.0) || (vertextype > 0.0 && previousProjected.z > 0.0)) {\n" + " dir = -dir;\n" + " clipped = true;\n" + " if (vertextype < 0.0 && nextProjected.y / nextProjected.w < -1.0) otherEndBelowFrustum = true;\n" + " else if (vertextype > 0.0 && previousProjected.y / previousProjected.w < -1.0) otherEndBelowFrustum = true;\n" + " } else {\n" + " primitivecolor = vec4(0.0,0.0,0.0,0.0);\n" + " gl_Position = vec4(-10000000.0, -1000000000.0, -1000000000.0, 1);\n" // get the vertex out of the way if the segment is fully invisible + " return;\n" + " }\n" + " } else if (triangletype < 2.0) {\n" // vertex in the view, try to miter + " //get directions from (C - B) and (B - A)\n" + " vec2 dirA = normalize((currentScreen - previousScreen));\n" + " if (previousProjected.z < 0.0) dirA = -dirA;\n" + " vec2 dirB = normalize((nextScreen - currentScreen));\n" + " //now compute the miter join normal and length\n" + " if (nextProjected.z < 0.0) dirB = -dirB;\n" + " vec2 tangent = normalize(dirA + dirB);\n" + " vec2 perp = vec2(-dirA.y, dirA.x);\n" + " vec2 vmiter = vec2(-tangent.y, tangent.x);\n" + " len = lineWidth / dot(vmiter, perp);\n" + // The following is an attempt to have a segment-length based miter threshold. + // A mediocre workaround until better mitering will be added. + " float lenTreshold = clamp( min(length((currentProjected.xy - previousProjected.xy) / aspectVec)," + " length((nextProjected.xy - currentProjected.xy) / aspectVec)), 3.0, 6.0 ) * 0.5;\n" + " if (len < lineWidth * lenTreshold && len > -lineWidth * lenTreshold \n" + " ) {\n" + " dir = tangent;\n" + " } else {\n" + " len = lineWidth;\n" + " }\n" + " }\n" + " vec4 offset;\n" + " if (!clipped) {\n" + " vec2 normal = normalize(vec2(-dir.y, dir.x));\n" + " normal *= len;\n" // fracZL apparently was needed before the (-2.0 / qt_Matrix[1][1]) factor was introduced + " normal /= aspectVec;\n" // straighten the normal up again + " float scaleFactor = currentProjected.w / centerProjected.w;\n" + " offset = vec4(normal * orientation * scaleFactor * (centerProjected.w / (-2.0 / qt_Matrix[1][1])), 0.0, 0.0);\n" // ToDo: figure out why (-2.0 / qt_Matrix[1][1]), that is empirically what works + " gl_Position = currentProjected + offset;\n" + " } else {\n" + " if (otherEndBelowFrustum) offset = vec4((dir * 1.0) / aspectVec, 0.0, 0.0);\n" // the if is necessary otherwise it seems the direction vector still flips in some obscure cases. + " else offset = vec4((dir * 500000000000.0) / aspectVec, 0.0, 0.0);\n" // Hack alert: just 1 triangle, long enough to look like a rectangle. + " if (vertextype < 0.0) gl_Position = nextProjected - offset; else gl_Position = previousProjected + offset;\n" + " }\n" + "}\n"; +} + QT_END_NAMESPACE + diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h b/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h index 3aa0f96b..9cd20ea5 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h @@ -59,8 +59,6 @@ QT_BEGIN_NAMESPACE -class MapPolylineNode; - class Q_LOCATION_PRIVATE_EXPORT QDeclarativeMapLineProperties : public QObject { Q_OBJECT @@ -86,53 +84,23 @@ private: QColor color_; }; -class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometry : public QGeoMapItemGeometry -{ -public: - QGeoMapPolylineGeometry(); - - void updateSourcePoints(const QGeoMap &map, - const QList<QDoubleVector2D> &path, - const QGeoCoordinate geoLeftBound); - - void updateScreenPoints(const QGeoMap &map, - qreal strokeWidth, - bool adjustTranslation = true); - - void clearSource(); - - bool contains(const QPointF &point) const override; - - QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map, - const QList<QDoubleVector2D> &path, - QDoubleVector2D &leftBoundWrapped); - - void pathToScreen(const QGeoMap &map, - const QList<QList<QDoubleVector2D> > &clippedPaths, - const QDoubleVector2D &leftBoundWrapped); - -public: - QVector<qreal> srcPoints_; - QVector<QPainterPath::ElementType> srcPointTypes_; - -#ifdef QT_LOCATION_DEBUG - QList<QDoubleVector2D> m_wrappedPath; - QList<QList<QDoubleVector2D>> m_clippedPaths; -#endif - - friend class QDeclarativeCircleMapItem; - friend class QDeclarativePolygonMapItem; - friend class QDeclarativeRectangleMapItem; -}; - +class QDeclarativePolylineMapItemPrivate; class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItem : public QDeclarativeGeoMapItemBase { Q_OBJECT + Q_ENUMS(Backend) Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QDeclarativeMapLineProperties *line READ line CONSTANT) + Q_PROPERTY(Backend backend READ backend WRITE setBackend NOTIFY backendChanged REVISION 15) public: + enum Backend { + Software = 0, + OpenGLLineStrip = 1, + OpenGLExtruded = 2, + }; + explicit QDeclarativePolylineMapItem(QQuickItem *parent = 0); ~QDeclarativePolylineMapItem(); @@ -159,70 +127,42 @@ public: QDeclarativeMapLineProperties *line(); + Backend backend() const; + void setBackend(Backend b); + Q_SIGNALS: void pathChanged(); - -protected: - void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; - void setPathFromGeoList(const QList<QGeoCoordinate> &path); - void updatePolish() override; + void backendChanged(); protected Q_SLOTS: void markSourceDirtyAndUpdate(); void updateAfterLinePropertiesChanged(); virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) override; -private: - void regenerateCache(); - void updateCache(); +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void setPathFromGeoList(const QList<QGeoCoordinate> &path); + void updatePolish() override; + void componentComplete() override; + void updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName); + void updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName, bool update); #ifdef QT_LOCATION_DEBUG public: #endif - QGeoPath geopath_; - QList<QDoubleVector2D> geopathProjected_; - QDeclarativeMapLineProperties line_; - QColor color_; - bool dirtyMaterial_; - QGeoMapPolylineGeometry geometry_; - bool updatingGeometry_; -}; + QGeoPath m_geopath; + QDeclarativeMapLineProperties m_line; -////////////////////////////////////////////////////////////////////// - -class Q_LOCATION_PRIVATE_EXPORT VisibleNode -{ -public: - VisibleNode(); - virtual ~VisibleNode(); + Backend m_backend = Software; + bool m_dirtyMaterial; + bool m_updatingGeometry; - bool subtreeBlocked() const; - void setSubtreeBlocked(bool blocked); - bool visible() const; - void setVisible(bool visible); + QScopedPointer<QDeclarativePolylineMapItemPrivate> m_d; - bool m_blocked : 1; - bool m_visible : 1; -}; - -class Q_LOCATION_PRIVATE_EXPORT MapItemGeometryNode : public QSGGeometryNode, public VisibleNode -{ -public: - ~MapItemGeometryNode() override; - bool isSubtreeBlocked() const override; -}; - -class Q_LOCATION_PRIVATE_EXPORT MapPolylineNode : public MapItemGeometryNode -{ -public: - MapPolylineNode(); - ~MapPolylineNode() override; - - void update(const QColor &fillColor, const QGeoMapItemGeometry *shape); - -private: - QSGFlatColorMaterial fill_material_; - QSGGeometry geometry_; + friend class QDeclarativePolylineMapItemPrivate; + friend class QDeclarativePolylineMapItemPrivateCPU; + friend class QDeclarativePolylineMapItemPrivateOpenGLLineStrip; + friend class QDeclarativePolylineMapItemPrivateOpenGLExtruded; }; QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h b/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h new file mode 100644 index 00000000..d37f77ad --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h @@ -0,0 +1,823 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 QDECLARATIVEPOLYLINEMAPITEM_P_P_H +#define QDECLARATIVEPOLYLINEMAPITEM_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 <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qdeclarativegeomapitemutils_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qgeomapitemgeometry_p.h> +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> +#include <QtPositioning/QGeoPath> +#include <QtPositioning/QGeoPolygon> +#include <QtPositioning/QGeoRectangle> +#include <QtPositioning/QGeoCircle> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QtCore/QScopedValueRollback> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapPolylineGeometry(); + + void updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + const QGeoCoordinate geoLeftBound); + + void updateScreenPoints(const QGeoMap &map, + qreal strokeWidth, + bool adjustTranslation = true); + + void clearSource(); + + bool contains(const QPointF &point) const override; + + QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + QDoubleVector2D &leftBoundWrapped); + + void pathToScreen(const QGeoMap &map, + const QList<QList<QDoubleVector2D> > &clippedPaths, + const QDoubleVector2D &leftBoundWrapped); + +public: + QVector<qreal> srcPoints_; + QVector<QPainterPath::ElementType> srcPointTypes_; + +#ifdef QT_LOCATION_DEBUG + QList<QDoubleVector2D> m_wrappedPath; + QList<QList<QDoubleVector2D>> m_clippedPaths; +#endif + + friend class QDeclarativeCircleMapItem; + friend class QDeclarativePolygonMapItem; + friend class QDeclarativeRectangleMapItem; +}; + +class Q_LOCATION_PRIVATE_EXPORT VisibleNode +{ +public: + VisibleNode(); + virtual ~VisibleNode(); + + bool subtreeBlocked() const; + void setSubtreeBlocked(bool blocked); + bool visible() const; + void setVisible(bool visible); + + bool m_blocked : 1; + bool m_visible : 1; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapItemGeometryNode : public QSGGeometryNode, public VisibleNode +{ +public: + ~MapItemGeometryNode() override; + bool isSubtreeBlocked() const override; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineMaterial : public QSGFlatColorMaterial +{ +public: + MapPolylineMaterial() + : 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; + } + + void setColor(const QColor &color) + { + QSGFlatColorMaterial::setColor(color); + setFlag(Blending, true); // ToDo: Needed only temporarily, can be removed after debugging + } + + int wrapOffset() const + { + return m_wrapOffset; + } + + void setWrapOffset(int wrapOffset) + { + m_wrapOffset = wrapOffset; + } + + void setLineWidth(const float lw) + { + m_lineWidth = lw; + } + + float lineWidth() const + { + return m_lineWidth; + } + + QSGMaterialType *type() const override; + int compare(const QSGMaterial *other) const override; + +protected: + QMatrix4x4 m_geoProjection; + QDoubleVector3D m_center; + int m_wrapOffset = 0; + float m_lineWidth = 1.0; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineNode : public MapItemGeometryNode +{ +public: + MapPolylineNode(); + ~MapPolylineNode() override; + + void update(const QColor &fillColor, const QGeoMapItemGeometry *shape); + +protected: + QSGFlatColorMaterial fill_material_; + QSGGeometry geometry_; +}; + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometryOpenGL : public QGeoMapItemGeometry +{ +public: + typedef struct { + QList<QDoubleVector2D> wrappedBboxes; + } WrappedPolyline; + + QGeoMapPolylineGeometryOpenGL() {} + + void updateSourcePoints(const QGeoMap &map, + const QGeoPolygon &poly); + + void updateSourcePoints(const QGeoMap &map, + const QGeoPath &poly); + + void updateSourcePoints(const QGeoProjectionWebMercator &p, + const QList<QDoubleVector2D> &wrappedPath, + const QGeoRectangle &boundingRectangle); + + void updateSourcePoints(const QGeoMap &map, + const QGeoRectangle &rect); + + void updateSourcePoints(const QGeoMap &map, + const QGeoCircle &circle); + + void updateScreenPoints(const QGeoMap &map, + qreal strokeWidth, + bool adjustTranslation = true); + + void updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth = 0.0); + + void allocateAndFillEntries(QSGGeometry *geom, bool closed = false) const; + void allocateAndFillLineStrip(QSGGeometry *geom) const; + + bool contains(const QPointF &point) const override + { + Q_UNUSED(point) + return false; + } + + static double distanceTo(const QDoubleVector2D &a, const QDoubleVector2D &b, const QDoubleVector2D &p) + { + double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared(); + QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) ); + + QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b; + + if (u > 0 && u < 1 + && (p-intersection).length() < (p-candidate).length() ) // And it falls in the segment + candidate = intersection; + + return qAbs((candidate - p).length()); + } + // Note: this is also slightly incorrect on joins and in the beginning/end of the line + bool contains(const QPointF &point, qreal lineWidth, const QGeoProjectionWebMercator &p) const + { + const double lineHalfWidth = lineWidth * 0.5; + const QDoubleVector2D pt(point); + QDoubleVector2D a; + if (m_screenVertices.size()) a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.first().toDoubleVector2D())); + QDoubleVector2D b; + for (int i = 1; i < m_screenVertices.size(); ++i) + { + if (!a.isFinite()) { + a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.at(i).toDoubleVector2D())); + continue; + } + + b = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.at(i).toDoubleVector2D())); + if (!b.isFinite()) { + a = b; + continue; + } + + if (b == a) + continue; + + // Heavily simplifying it here: if a point is not projectable, skip the segment. + // For a correct solution, the segment should be clipped instead. + if (distanceTo(a, b, pt) <= lineHalfWidth) + return true; + + a = b; + } + return false; + } + +public: + QVector<QDeclarativeGeoMapItemUtils::vec2> m_screenVertices; + QDoubleVector2D m_bboxLeftBoundWrapped; + QVector<WrappedPolyline> m_wrappedPolygons; + int m_wrapOffset; + + friend class QDeclarativeCircleMapItem; + friend class QDeclarativePolygonMapItem; + friend class QDeclarativeRectangleMapItem; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineShaderLineStrip : public QSGMaterialShader +{ +public: + MapPolylineShaderLineStrip(); + + 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 { + 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; + +protected: + 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 MapPolylineShaderExtruded : public QSGMaterialShader +{ +public: + MapPolylineShaderExtruded(); + + // Heavily adapted from https://github.com/mattdesl/webgl-lines/blob/master/projected/vert.glsl, + // that is (c) Matt DesLauriers, and released under the MIT license. + const char *vertexShaderMiteredSegments() const; + + const char *vertexShader() const override + { + return vertexShaderMiteredSegments(); + } + + const char *fragmentShader() const override + { + return + "varying vec4 primitivecolor; \n" + "void main() { \n" + " gl_FragColor = primitivecolor; \n" + "}"; + } + + void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; + char const *const *attributeNames() const override; + +protected: + 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_lineWidth_id = program()->uniformLocation("lineWidth"); + m_aspect_id = program()->uniformLocation("aspect"); + m_miter_id = program()->uniformLocation("miter"); + 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_lineWidth_id; + int m_aspect_id; + int m_miter_id; + int m_wrapOffset_id; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineNodeOpenGLLineStrip : public MapItemGeometryNode +{ +public: + MapPolylineNodeOpenGLLineStrip(); + ~MapPolylineNodeOpenGLLineStrip() override; + + void update(const QColor &fillColor, + const qreal lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 &geoProjection, + const QDoubleVector3D ¢er, + const Qt::PenCapStyle capStyle = Qt::SquareCap); + +protected: + MapPolylineMaterial fill_material_; + QSGGeometry geometry_; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineMaterialExtruded : public MapPolylineMaterial +{ +public: + MapPolylineMaterialExtruded() : MapPolylineMaterial() + { + + } + QSGMaterialShader *createShader() const override; + + void setMiter(const int m) + { + m_miter = m; + } + + int miter() const + { + return m_miter; + } + + QSGMaterialType *type() const override; + int compare(const QSGMaterial *other) const override; + + int m_miter = 0; +}; + +class Q_LOCATION_PRIVATE_EXPORT MapPolylineNodeOpenGLExtruded : public MapItemGeometryNode +{ +public: + + typedef struct { + QDeclarativeGeoMapItemUtils::vec2 pos; + QDeclarativeGeoMapItemUtils::vec2 prev; + QDeclarativeGeoMapItemUtils::vec2 next; + float direction; + float triangletype; // es2 does not support int attribs + float vertextype; + + static const char * const *attributeNames() + { + static char const *const attr[] = { "vertex", "previous", "next", "direction", "triangletype", "vertextype", nullptr }; + return attr; + } + static const QSGGeometry::AttributeSet &attributes() + { + static const QSGGeometry::Attribute dataTri[] = { + QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute) // pos + ,QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // next + ,QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // previous + ,QSGGeometry::Attribute::createWithAttributeType(3, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // direction + ,QSGGeometry::Attribute::createWithAttributeType(4, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // triangletype + ,QSGGeometry::Attribute::createWithAttributeType(5, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // vertextype + }; + static const QSGGeometry::AttributeSet attrsTri = { 6, sizeof(MapPolylineNodeOpenGLExtruded::MapPolylineEntry), dataTri }; + return attrsTri; + } + } MapPolylineEntry; + + MapPolylineNodeOpenGLExtruded(); + ~MapPolylineNodeOpenGLExtruded() override; + + void update(const QColor &fillColor, + const float lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 geoProjection, + const QDoubleVector3D center, + const Qt::PenCapStyle capStyle = Qt::FlatCap, + bool closed = false); + + static const QSGGeometry::AttributeSet &attributesMapPolylineTriangulated(); + +protected: + MapPolylineMaterialExtruded fill_material_; + QSGGeometry m_geometryTriangulating; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivate +{ +public: + QDeclarativePolylineMapItemPrivate(QDeclarativePolylineMapItem &poly) : m_poly(poly) + { + + } + QDeclarativePolylineMapItemPrivate(QDeclarativePolylineMapItemPrivate &other) : m_poly(other.m_poly) + { + } + + virtual ~QDeclarativePolylineMapItemPrivate(); + virtual void markSourceDirtyAndUpdate() = 0; + virtual void onMapSet() = 0; + virtual void onLinePropertiesChanged() = 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; + + QDeclarativePolylineMapItem &m_poly; + Qt::PenStyle m_penStyle = Qt::SolidLine; + Qt::PenCapStyle m_penCapStyle = Qt::SquareCap; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateCPU: public QDeclarativePolylineMapItemPrivate +{ +public: + QDeclarativePolylineMapItemPrivateCPU(QDeclarativePolylineMapItem &poly) : QDeclarativePolylineMapItemPrivate(poly) + { + } + + QDeclarativePolylineMapItemPrivateCPU(QDeclarativePolylineMapItemPrivate &other) + : QDeclarativePolylineMapItemPrivate(other) + { + } + + ~QDeclarativePolylineMapItemPrivateCPU() override; + void onLinePropertiesChanged() override + { + // mark dirty just in case we're a width change + markSourceDirtyAndUpdate(); + } + void markSourceDirtyAndUpdate() override + { + m_geometry.markSourceDirty(); + m_poly.polishAndUpdate(); + } + void regenerateCache() + { + if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection()); + m_geopathProjected.clear(); + m_geopathProjected.reserve(m_poly.m_geopath.size()); + for (const QGeoCoordinate &c : m_poly.m_geopath.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<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection()); + m_geopathProjected << p.geoToMapProjection(m_poly.m_geopath.path().last()); + } + void preserveGeometry() + { + m_geometry.setPreserveGeometry(true, m_poly.m_geopath.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_geopath.path().length() < 2) { // Possibly cleared + m_geometry.clear(); + m_poly.setWidth(0); + m_poly.setHeight(0); + return; + } + QScopedValueRollback<bool> rollback(m_poly.m_updatingGeometry); + m_poly.m_updatingGeometry = true; + + const QGeoMap *map = m_poly.map(); + const qreal borderWidth = m_poly.m_line.width(); + + m_geometry.updateSourcePoints(*map, m_geopathProjected, m_poly.m_geopath.boundingGeoRectangle().topLeft()); + m_geometry.updateScreenPoints(*map, borderWidth); + + m_poly.setWidth(m_geometry.sourceBoundingBox().width() + borderWidth); + m_poly.setHeight(m_geometry.sourceBoundingBox().height() + borderWidth); + + m_poly.setPositionOnMap(m_geometry.origin(), -1 * m_geometry.sourceBoundingBox().topLeft() + + QPointF(borderWidth, borderWidth) * 0.5 ); // it has to be shifted so that the center of the line is on the correct geocoord + } + QSGNode *updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * /*data*/) override + { + if (!m_node || !oldNode) { + m_node = new MapPolylineNode(); + if (oldNode) { + delete oldNode; + oldNode = nullptr; + } + } else { + m_node = static_cast<MapPolylineNode *>(oldNode); + } + + //TODO: update only material + if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial || !oldNode) { + m_node->update(m_poly.m_line.color(), &m_geometry); + m_geometry.setPreserveGeometry(false); + m_geometry.markClean(); + m_poly.m_dirtyMaterial = false; + } + return m_node; + } + bool contains(const QPointF &point) const override + { + return m_geometry.contains(point); + } + + QList<QDoubleVector2D> m_geopathProjected; + QGeoMapPolylineGeometry m_geometry; + MapPolylineNode *m_node = nullptr; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateOpenGLLineStrip: public QDeclarativePolylineMapItemPrivate +{ +public: + + QDeclarativePolylineMapItemPrivateOpenGLLineStrip(QDeclarativePolylineMapItem &poly) : QDeclarativePolylineMapItemPrivate(poly) + { + } + + QDeclarativePolylineMapItemPrivateOpenGLLineStrip(QDeclarativePolylineMapItemPrivate &other) + : QDeclarativePolylineMapItemPrivate(other) + { + } + + ~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() override; + void onLinePropertiesChanged() override + { + afterViewportChanged(); + } + void markSourceDirtyAndUpdate() override + { + m_geometry.markSourceDirty(); + m_poly.polishAndUpdate(); + } + void preserveGeometry() + { + m_geometry.setPreserveGeometry(true, m_poly.m_geopath.boundingGeoRectangle().topLeft()); + } + void onMapSet() override + { + markSourceDirtyAndUpdate(); + } + void onGeoGeometryChanged() override + { + preserveGeometry(); + markSourceDirtyAndUpdate(); + } + void onGeoGeometryUpdated() override + { + preserveGeometry(); + markSourceDirtyAndUpdate(); + } + void onItemGeometryChanged() override + { + onGeoGeometryChanged(); + } + void afterViewportChanged() override + { + preserveGeometry(); + m_poly.polishAndUpdate(); + } + bool contains(const QPointF &point) const override + { + return m_geometry.contains(m_poly.mapToItem(m_poly.quickMap(), point), + m_poly.line()->width(), + static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection())); + } + void updatePolish() override + { + if (m_poly.m_geopath.path().length() == 0) { // Possibly cleared + m_geometry.clear(); + m_geometry.clear(); + m_poly.setWidth(0); + m_poly.setHeight(0); + return; + } + + QScopedValueRollback<bool> rollback(m_poly.m_updatingGeometry); + m_poly.m_updatingGeometry = true; + const qreal lineWidth = m_poly.m_line.width(); + m_geometry.updateSourcePoints(*m_poly.map(), m_poly.m_geopath); + m_geometry.markScreenDirty(); + m_geometry.updateScreenPoints(*m_poly.map(), lineWidth); + + m_poly.setWidth(m_geometry.sourceBoundingBox().width()); + m_poly.setHeight(m_geometry.sourceBoundingBox().height()); + m_poly.setPosition(1.0 * m_geometry.firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); + } + QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override + { + Q_UNUSED(data); + + if (!m_node || !oldNode) { + m_node = new MapPolylineNodeOpenGLLineStrip(); + if (oldNode) + delete oldNode; + } else { + m_node = static_cast<MapPolylineNodeOpenGLLineStrip *>(oldNode); + } + + if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial) { + const QGeoMap *map = m_poly.map(); + const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform(); + const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator(); + m_node->update(m_poly.m_line.color(), // This updates only the material if the geometry is unchanged + m_poly.m_line.width(), + &m_geometry, + combinedMatrix, + cameraCenter); + m_geometry.setPreserveGeometry(false); + m_geometry.markClean(); + m_poly.m_dirtyMaterial = false; + } + return m_node; + } + + QGeoMapPolylineGeometryOpenGL m_geometry; + MapPolylineNodeOpenGLLineStrip *m_node = nullptr; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateOpenGLExtruded: public QDeclarativePolylineMapItemPrivateOpenGLLineStrip +{ +public: + + QDeclarativePolylineMapItemPrivateOpenGLExtruded(QDeclarativePolylineMapItem &poly) + : QDeclarativePolylineMapItemPrivateOpenGLLineStrip(poly) + { + } + + QDeclarativePolylineMapItemPrivateOpenGLExtruded(QDeclarativePolylineMapItemPrivate &other) + : QDeclarativePolylineMapItemPrivateOpenGLLineStrip(other) + { + } + + ~QDeclarativePolylineMapItemPrivateOpenGLExtruded() override; + + QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override + { + Q_UNUSED(data); + const QGeoMap *map = m_poly.map(); + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map->geoProjection()); + const QMatrix4x4 &combinedMatrix = p.qsgTransform(); + const QDoubleVector3D &cameraCenter = p.centerMercator(); + const QColor &color = m_poly.m_line.color(); + const float lineWidth = m_poly.m_line.width(); + + MapPolylineNodeOpenGLExtruded *nodeTri = nullptr; + if (!m_nodeTri || !oldNode) { + if (oldNode) + delete oldNode; + nodeTri = new MapPolylineNodeOpenGLExtruded(); + } else { + nodeTri = static_cast<MapPolylineNodeOpenGLExtruded *>(oldNode); + } + + //TODO: update only material + if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial) { + nodeTri->update(color, + lineWidth , + &m_geometry, + combinedMatrix, + cameraCenter, + m_penCapStyle); + m_geometry.setPreserveGeometry(false); + m_geometry.markClean(); + m_poly.m_dirtyMaterial = false; + } + m_nodeTri = nodeTri; + return nodeTri; + } + + MapPolylineNodeOpenGLExtruded *m_nodeTri = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVEPOLYLINEMAPITEM_P_P_H diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp b/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp index fd4109a7..74d2cc13 100644 --- a/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp @@ -35,6 +35,7 @@ ****************************************************************************/ #include "qdeclarativerectanglemapitem_p.h" +#include "qdeclarativerectanglemapitem_p_p.h" #include "qdeclarativepolygonmapitem_p.h" #include "qlocationutils_p.h" #include <QPainterPath> @@ -125,16 +126,30 @@ QT_BEGIN_NAMESPACE \since 5.14 */ +struct RectangleBackendSelector +{ + RectangleBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativeRectangleMapItem::OpenGL : QDeclarativeRectangleMapItem::Software; + } + QDeclarativeRectangleMapItem::Backend backend = QDeclarativeRectangleMapItem::Software; +}; + +Q_GLOBAL_STATIC(RectangleBackendSelector, mapRectangleBackendSelector) + QDeclarativeRectangleMapItem::QDeclarativeRectangleMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), - updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), + m_updatingGeometry(false) + , m_d(new QDeclarativeRectangleMapItemPrivateCPU(*this)) { + // ToDo: handle envvar, and switch implementation. m_itemType = QGeoMap::MapRectangle; setFlag(ItemHasContents, true); - QObject::connect(&border_, SIGNAL(colorChanged(QColor)), - this, SLOT(markSourceDirtyAndUpdate())); - QObject::connect(&border_, SIGNAL(widthChanged(qreal)), - this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&m_border, SIGNAL(colorChanged(QColor)), + this, SLOT(onLinePropertiesChanged())); + QObject::connect(&m_border, SIGNAL(widthChanged(qreal)), + this, SLOT(onLinePropertiesChanged())); + setBackend(mapRectangleBackendSelector->backend); } QDeclarativeRectangleMapItem::~QDeclarativeRectangleMapItem() @@ -142,6 +157,43 @@ QDeclarativeRectangleMapItem::~QDeclarativeRectangleMapItem() } /*! + \qmlproperty MapRectangle.Backend QtLocation::MapRectangle::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapRectangle.Software and \b{MapRectangle.OpenGL}. + The default value is \b{MapRectangle.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ +QDeclarativeRectangleMapItem::Backend QDeclarativeRectangleMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativeRectangleMapItem::setBackend(QDeclarativeRectangleMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativeRectangleMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativeRectangleMapItemPrivate *>(new QDeclarativeRectangleMapItemPrivateCPU(*this)) + : static_cast<QDeclarativeRectangleMapItemPrivate * >(new QDeclarativeRectangleMapItemPrivateOpenGL(*this))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); +} + +/*! \internal */ void QDeclarativeRectangleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) @@ -149,8 +201,7 @@ void QDeclarativeRectangleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap QDeclarativeGeoMapItemBase::setMap(quickMap,map); if (!map) return; - updatePath(); - markSourceDirtyAndUpdate(); + m_d->onMapSet(); } /*! @@ -167,7 +218,7 @@ void QDeclarativeRectangleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap */ QDeclarativeMapLineProperties *QDeclarativeRectangleMapItem::border() { - return &border_; + return &m_border; } /*! @@ -178,18 +229,17 @@ QDeclarativeMapLineProperties *QDeclarativeRectangleMapItem::border() */ void QDeclarativeRectangleMapItem::setTopLeft(const QGeoCoordinate &topLeft) { - if (rectangle_.topLeft() == topLeft) + if (m_rectangle.topLeft() == topLeft) return; - rectangle_.setTopLeft(topLeft); - updatePath(); - markSourceDirtyAndUpdate(); + m_rectangle.setTopLeft(topLeft); + m_d->onGeoGeometryChanged(); emit topLeftChanged(topLeft); } QGeoCoordinate QDeclarativeRectangleMapItem::topLeft() { - return rectangle_.topLeft(); + return m_rectangle.topLeft(); } /*! @@ -197,9 +247,12 @@ QGeoCoordinate QDeclarativeRectangleMapItem::topLeft() */ void QDeclarativeRectangleMapItem::markSourceDirtyAndUpdate() { - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); +} + +void QDeclarativeRectangleMapItem::onLinePropertiesChanged() +{ + m_d->onLinePropertiesChanged(); } /*! @@ -210,18 +263,17 @@ void QDeclarativeRectangleMapItem::markSourceDirtyAndUpdate() */ void QDeclarativeRectangleMapItem::setBottomRight(const QGeoCoordinate &bottomRight) { - if (rectangle_.bottomRight() == bottomRight) + if (m_rectangle.bottomRight() == bottomRight) return; - rectangle_.setBottomRight(bottomRight); - updatePath(); - markSourceDirtyAndUpdate(); + m_rectangle.setBottomRight(bottomRight); + m_d->onGeoGeometryChanged(); emit bottomRightChanged(bottomRight); } QGeoCoordinate QDeclarativeRectangleMapItem::bottomRight() { - return rectangle_.bottomRight(); + return m_rectangle.bottomRight(); } /*! @@ -232,17 +284,17 @@ QGeoCoordinate QDeclarativeRectangleMapItem::bottomRight() */ QColor QDeclarativeRectangleMapItem::color() const { - return color_; + return m_color; } void QDeclarativeRectangleMapItem::setColor(const QColor &color) { - if (color_ == color) + if (m_color == color) return; - color_ = color; - dirtyMaterial_ = true; + m_color = color; + m_dirtyMaterial = true; polishAndUpdate(); - emit colorChanged(color_); + emit colorChanged(m_color); } /*! @@ -260,24 +312,7 @@ void QDeclarativeRectangleMapItem::setColor(const QColor &color) */ QSGNode *QDeclarativeRectangleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - - MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); - - if (!node) { - node = new MapPolygonNode(); - } - - //TODO: update only material - if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { - node->update(color_, border_.color(), &geometry_, &borderGeometry_); - geometry_.setPreserveGeometry(false); - borderGeometry_.setPreserveGeometry(false); - geometry_.markClean(); - borderGeometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } /*! @@ -287,55 +322,7 @@ void QDeclarativeRectangleMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - if (!topLeft().isValid() || !bottomRight().isValid()) { - geometry_.clear(); - borderGeometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - geometry_.setPreserveGeometry(true, rectangle_.topLeft()); - geometry_.updateSourcePoints(*map(), pathMercator_); - geometry_.updateScreenPoints(*map(), border_.width()); - - QList<QGeoMapItemGeometry *> geoms; - geoms << &geometry_; - borderGeometry_.clear(); - - if (border_.color() != Qt::transparent && border_.width() > 0) { - QList<QDoubleVector2D> closedPath = pathMercator_; - closedPath << closedPath.first(); - - borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); - const QGeoCoordinate &geometryOrigin = geometry_.origin(); - - borderGeometry_.srcPoints_.clear(); - borderGeometry_.srcPointTypes_.clear(); - - QDoubleVector2D borderLeftBoundWrapped; - QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); - if (clippedPaths.size()) { - borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); - borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); - borderGeometry_.updateScreenPoints(*map(), border_.width()); - - geoms << &borderGeometry_; - } else { - borderGeometry_.clear(); - } - } - - QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); - setWidth(combined.width() + 2 * border_.width()); - setHeight(combined.height() + 2 * border_.width()); - - setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); + m_d->updatePolish(); } /*! @@ -345,10 +332,7 @@ void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportCha { if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) return; - - geometry_.setPreserveGeometry(true, rectangle_.topLeft()); - borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); - markSourceDirtyAndUpdate(); + m_d->afterViewportChanged(); } /*! @@ -356,46 +340,29 @@ void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportCha */ bool QDeclarativeRectangleMapItem::contains(const QPointF &point) const { - return (geometry_.contains(point) || borderGeometry_.contains(point)); + return m_d->contains(point); } const QGeoShape &QDeclarativeRectangleMapItem::geoShape() const { - return rectangle_; + return m_rectangle; } void QDeclarativeRectangleMapItem::setGeoShape(const QGeoShape &shape) { - if (shape == rectangle_) + if (shape == m_rectangle) return; - const QGeoRectangle rectangle = rectangle_.boundingGeoRectangle(); - const bool tlHasChanged = rectangle.topLeft() != rectangle_.topLeft(); - const bool brHasChanged = rectangle.bottomRight() != rectangle_.bottomRight(); - rectangle_ = rectangle; + const QGeoRectangle rectangle = m_rectangle.boundingGeoRectangle(); + const bool tlHasChanged = rectangle.topLeft() != m_rectangle.topLeft(); + const bool brHasChanged = rectangle.bottomRight() != m_rectangle.bottomRight(); + m_rectangle = rectangle; - updatePath(); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); if (tlHasChanged) - emit topLeftChanged(rectangle_.topLeft()); + emit topLeftChanged(m_rectangle.topLeft()); if (brHasChanged) - emit bottomRightChanged(rectangle_.bottomRight()); -} - -/*! - \internal -*/ -void QDeclarativeRectangleMapItem::updatePath() -{ - if (!map()) - return; - pathMercator_.clear(); - pathMercator_ << QWebMercator::coordToMercator(rectangle_.topLeft()); - pathMercator_ << QWebMercator::coordToMercator( - QGeoCoordinate(rectangle_.topLeft().latitude(), rectangle_.bottomRight().longitude())); - pathMercator_ << QWebMercator::coordToMercator(rectangle_.bottomRight()); - pathMercator_ << QWebMercator::coordToMercator( - QGeoCoordinate(rectangle_.bottomRight().latitude(), rectangle_.topLeft().longitude())); + emit bottomRightChanged(m_rectangle.bottomRight()); } /*! @@ -403,7 +370,7 @@ void QDeclarativeRectangleMapItem::updatePath() */ void QDeclarativeRectangleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !rectangle_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + if (!map() || !m_rectangle.isValid() || m_updatingGeometry || newGeometry.topLeft() == oldGeometry.topLeft()) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } @@ -417,16 +384,19 @@ void QDeclarativeRectangleMapItem::geometryChanged(const QRectF &newGeometry, co if (offsetLati == 0.0 && offsetLongi == 0.0) return; - rectangle_.translate(offsetLati, offsetLongi); - updatePath(); - geometry_.setPreserveGeometry(true, rectangle_.topLeft()); - borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); - markSourceDirtyAndUpdate(); - emit topLeftChanged(rectangle_.topLeft()); - emit bottomRightChanged(rectangle_.bottomRight()); + m_rectangle.translate(offsetLati, offsetLongi); + m_d->onItemGeometryChanged(); + emit topLeftChanged(m_rectangle.topLeft()); + emit bottomRightChanged(m_rectangle.bottomRight()); // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested // call to this function. } +QDeclarativeRectangleMapItemPrivate::~QDeclarativeRectangleMapItemPrivate() {} + +QDeclarativeRectangleMapItemPrivateCPU::~QDeclarativeRectangleMapItemPrivateCPU() {} + +QDeclarativeRectangleMapItemPrivateOpenGL::~QDeclarativeRectangleMapItemPrivateOpenGL() {} + QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h b/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h index 790b99d9..a9ce2f4c 100644 --- a/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h @@ -52,7 +52,7 @@ #include <QtLocation/private/qdeclarativegeomapitembase_p.h> #include <QtLocation/private/qgeomapitemgeometry_p.h> #include <QtLocation/private/qdeclarativepolylinemapitem_p.h> -#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p_p.h> #include <QtPositioning/private/qdoublevector2d_p.h> #include <QSGGeometryNode> @@ -60,18 +60,26 @@ QT_BEGIN_NAMESPACE +class QDeclarativeRectangleMapItemPrivate; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItem: public QDeclarativeGeoMapItemBase { Q_OBJECT + Q_ENUMS(Backend) Q_PROPERTY(QGeoCoordinate topLeft READ topLeft WRITE setTopLeft NOTIFY topLeftChanged) Q_PROPERTY(QGeoCoordinate bottomRight READ bottomRight WRITE setBottomRight NOTIFY bottomRightChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + Q_PROPERTY(Backend backend READ backend WRITE setBackend NOTIFY backendChanged REVISION 15) public: - explicit QDeclarativeRectangleMapItem(QQuickItem *parent = 0); - ~QDeclarativeRectangleMapItem(); + enum Backend { + Software = 0, + OpenGL = 1 + }; + + explicit QDeclarativeRectangleMapItem(QQuickItem *parent = nullptr); + ~QDeclarativeRectangleMapItem() override; virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) override; //from QuickItem @@ -92,29 +100,38 @@ public: const QGeoShape &geoShape() const override; void setGeoShape(const QGeoShape &shape) override; + Backend backend() const; + void setBackend(Backend b); + Q_SIGNALS: void topLeftChanged(const QGeoCoordinate &topLeft); void bottomRightChanged(const QGeoCoordinate &bottomRight); void colorChanged(const QColor &color); + void backendChanged(); protected: - void updatePath(); void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; void updatePolish() override; protected Q_SLOTS: void markSourceDirtyAndUpdate(); + void onLinePropertiesChanged(); virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) override; private: - QGeoRectangle rectangle_; - QDeclarativeMapLineProperties border_; - QColor color_; - bool dirtyMaterial_; - QGeoMapPolygonGeometry geometry_; - QGeoMapPolylineGeometry borderGeometry_; - bool updatingGeometry_; - QList<QDoubleVector2D> pathMercator_; + QGeoRectangle m_rectangle; + QDeclarativeMapLineProperties m_border; + QColor m_color; + bool m_dirtyMaterial; + + bool m_updatingGeometry; + Backend m_backend = Software; + + QScopedPointer<QDeclarativeRectangleMapItemPrivate> m_d; + + friend class QDeclarativeRectangleMapItemPrivate; + friend class QDeclarativeRectangleMapItemPrivateCPU; + friend class QDeclarativeRectangleMapItemPrivateOpenGL; }; ////////////////////////////////////////////////////////////////////// diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h b/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h new file mode 100644 index 00000000..520abdf3 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 QDECLARATIVERECTANGLEMAPITEM_P_P_H +#define QDECLARATIVERECTANGLEMAPITEM_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 <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p_p.h> +#include <QtLocation/private/qdeclarativerectanglemapitem_p.h> +#include <QtPositioning/private/qwebmercator_p.h> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivate +{ +public: + QDeclarativeRectangleMapItemPrivate(QDeclarativeRectangleMapItem &rect) : m_rect(rect) + { + + } + QDeclarativeRectangleMapItemPrivate(QDeclarativeRectangleMapItemPrivate &other) : m_rect(other.m_rect) + { + } + + virtual ~QDeclarativeRectangleMapItemPrivate(); + virtual void onLinePropertiesChanged() = 0; + virtual void markSourceDirtyAndUpdate() = 0; + virtual void onMapSet() = 0; + virtual void onGeoGeometryChanged() = 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; + + QDeclarativeRectangleMapItem &m_rect; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivateCPU: public QDeclarativeRectangleMapItemPrivate +{ +public: + QDeclarativeRectangleMapItemPrivateCPU(QDeclarativeRectangleMapItem &rect) : QDeclarativeRectangleMapItemPrivate(rect) + { + } + + QDeclarativeRectangleMapItemPrivateCPU(QDeclarativeRectangleMapItemPrivate &other) + : QDeclarativeRectangleMapItemPrivate(other) + { + } + + ~QDeclarativeRectangleMapItemPrivateCPU() override; + + void onLinePropertiesChanged() override + { + // mark dirty just in case we're a width change + markSourceDirtyAndUpdate(); + } + virtual void markSourceDirtyAndUpdate() override + { + m_geometry.markSourceDirty(); + m_borderGeometry.markSourceDirty(); + m_rect.polishAndUpdate(); + } + virtual void onMapSet() override + { + markSourceDirtyAndUpdate(); + } + virtual void onGeoGeometryChanged() override + { + markSourceDirtyAndUpdate(); + } + virtual void onItemGeometryChanged() override + { + m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + m_borderGeometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + markSourceDirtyAndUpdate(); + } + virtual void afterViewportChanged() override + { + m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + m_borderGeometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + markSourceDirtyAndUpdate(); + } + virtual void updatePolish() override + { + if (!m_rect.topLeft().isValid() || !m_rect.bottomRight().isValid()) { + m_geometry.clear(); + m_borderGeometry.clear(); + m_rect.setWidth(0); + m_rect.setHeight(0); + return; + } + + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_rect.map()->geoProjection()); + + QScopedValueRollback<bool> rollback(m_rect.m_updatingGeometry); + m_rect.m_updatingGeometry = true; + + const QList<QGeoCoordinate> perimeter = path(m_rect.m_rectangle); + const QList<QDoubleVector2D> pathMercator_ = pathMercator(perimeter); + m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + m_geometry.updateSourcePoints(*m_rect.map(), pathMercator_); + m_geometry.updateScreenPoints(*m_rect.map(), m_rect.m_border.width()); + + QList<QGeoMapItemGeometry *> geoms; + geoms << &m_geometry; + m_borderGeometry.clear(); + + if (m_rect.m_border.color().alpha() != 0 && m_rect.m_border.width() > 0) { + QList<QDoubleVector2D> closedPath = pathMercator_; + closedPath << closedPath.first(); + + m_borderGeometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + const QGeoCoordinate &geometryOrigin = m_geometry.origin(); + + m_borderGeometry.srcPoints_.clear(); + m_borderGeometry.srcPointTypes_.clear(); + + QDoubleVector2D borderLeftBoundWrapped; + QList<QList<QDoubleVector2D > > clippedPaths = m_borderGeometry.clipPath(*m_rect.map(), closedPath, borderLeftBoundWrapped); + if (clippedPaths.size()) { + borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); + m_borderGeometry.pathToScreen(*m_rect.map(), clippedPaths, borderLeftBoundWrapped); + m_borderGeometry.updateScreenPoints(*m_rect.map(), m_rect.m_border.width()); + + geoms << &m_borderGeometry; + } else { + m_borderGeometry.clear(); + } + } + + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + m_rect.setWidth(combined.width() + 2 * m_rect.m_border.width()); // ToDo: fix this! 2 is incorrect + m_rect.setHeight(combined.height() + 2 * m_rect.m_border.width()); + + m_rect.setPositionOnMap(m_geometry.origin(), m_geometry.firstPointOffset()); + } + + virtual 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<MapPolygonNode *>(oldNode); + } + + //TODO: update only material + if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_rect.m_dirtyMaterial) { + m_node->update(m_rect.m_color, m_rect.m_border.color(), &m_geometry, &m_borderGeometry); + m_geometry.setPreserveGeometry(false); + m_borderGeometry.setPreserveGeometry(false); + m_geometry.markClean(); + m_borderGeometry.markClean(); + m_rect.m_dirtyMaterial = false; + } + return m_node; + } + virtual bool contains(const QPointF &point) const override + { + return (m_geometry.contains(point) || m_borderGeometry.contains(point)); + } + + static QList<QGeoCoordinate> path(const QGeoRectangle &rect) + { + QList<QGeoCoordinate> res; + res << rect.topLeft(); + res << QGeoCoordinate(rect.topLeft().latitude(), rect.bottomRight().longitude()); + res << rect.bottomRight(); + res << QGeoCoordinate(rect.bottomRight().latitude(), rect.topLeft().longitude()); + return res; + } + + static QList<QGeoCoordinate> perimeter(const QGeoRectangle &rect) + { + QList<QGeoCoordinate> res; + res << rect.topLeft(); + res << QGeoCoordinate(rect.topLeft().latitude(), rect.bottomRight().longitude()); + res << rect.bottomRight(); + res << QGeoCoordinate(rect.bottomRight().latitude(), rect.topLeft().longitude()); + res << res.first(); + return res; + } + + static QList<QDoubleVector2D> pathMercator(const QList<QGeoCoordinate> &p) + { + QList<QDoubleVector2D> res; + for (const auto &c: p) + res << QWebMercator::coordToMercator(c); + return res; + } + + QGeoMapPolygonGeometry m_geometry; + QGeoMapPolylineGeometry m_borderGeometry; + MapPolygonNode *m_node = nullptr; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivateOpenGL: public QDeclarativeRectangleMapItemPrivate +{ +public: + QDeclarativeRectangleMapItemPrivateOpenGL(QDeclarativeRectangleMapItem &rect) : QDeclarativeRectangleMapItemPrivate(rect) + { + } + + QDeclarativeRectangleMapItemPrivateOpenGL(QDeclarativeRectangleMapItemPrivate &other) + : QDeclarativeRectangleMapItemPrivate(other) + { + } + + ~QDeclarativeRectangleMapItemPrivateOpenGL() override; + + void markScreenDirtyAndUpdate() + { + // preserveGeometry is cleared in updateMapItemPaintNode + m_geometry.markScreenDirty(); + m_borderGeometry.markScreenDirty(); + m_rect.polishAndUpdate(); + } + void onLinePropertiesChanged() override + { + m_rect.m_dirtyMaterial = true; + afterViewportChanged(); + } + virtual void markSourceDirtyAndUpdate() override + { + m_geometry.markSourceDirty(); + m_borderGeometry.markSourceDirty(); + m_rect.polishAndUpdate(); + } + void preserveGeometry() + { + m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + m_borderGeometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft()); + } + virtual void onMapSet() override + { + markSourceDirtyAndUpdate(); + } + virtual void onGeoGeometryChanged() override + { + preserveGeometry(); + markSourceDirtyAndUpdate(); + } + virtual void onItemGeometryChanged() override + { + onGeoGeometryChanged(); + } + virtual void afterViewportChanged() override + { + preserveGeometry(); + markScreenDirtyAndUpdate(); + } + virtual void updatePolish() override + { + if (!m_rect.topLeft().isValid() || !m_rect.bottomRight().isValid()) { + m_geometry.clear(); + m_borderGeometry.clear(); + m_rect.setWidth(0); + m_rect.setHeight(0); + return; + } + + QScopedValueRollback<bool> rollback(m_rect.m_updatingGeometry); + m_rect.m_updatingGeometry = true; + const qreal lineWidth = m_rect.m_border.width(); + const QColor &lineColor = m_rect.m_border.color(); + const QColor &fillColor = m_rect.color(); + if (fillColor.alpha() != 0) { + m_geometry.updateSourcePoints(*m_rect.map(), m_rect.m_rectangle); + m_geometry.markScreenDirty(); + m_geometry.updateScreenPoints(*m_rect.map(), lineWidth, lineColor); + } else { + m_geometry.clearBounds(); + } + + QGeoMapItemGeometry * geom = &m_geometry; + m_borderGeometry.clearScreen(); + if (lineColor.alpha() != 0 && lineWidth > 0) { + m_borderGeometry.updateSourcePoints(*m_rect.map(), m_rect.m_rectangle); + m_borderGeometry.markScreenDirty(); + m_borderGeometry.updateScreenPoints(*m_rect.map(), lineWidth); + geom = &m_borderGeometry; + } + m_rect.setWidth(geom->sourceBoundingBox().width()); + m_rect.setHeight(geom->sourceBoundingBox().height()); + m_rect.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); + } + + virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override + { + Q_UNUSED(data); + + if (!m_rootNode || !oldNode) { + m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::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<QDeclarativePolygonMapItemPrivateOpenGL::RootNode *>(oldNode); + } + + const QGeoMap *map = m_rect.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_rect.m_border.color(), + float(m_rect.m_border.width()), + &m_borderGeometry, + combinedMatrix, + cameraCenter, + Qt::SquareCap, + true); + m_borderGeometry.setPreserveGeometry(false); + m_borderGeometry.markClean(); + } else { + m_polylinenode->setSubtreeBlocked(true); + } + if (m_geometry.isScreenDirty()) { + m_node->update(m_rect.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; + } + virtual bool contains(const QPointF &point) const override + { + const qreal lineWidth = m_rect.m_border.width(); + const QColor &lineColor = m_rect.m_border.color(); + const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); + if (bounds.contains(point)) { + QDeclarativeGeoMap *m = m_rect.quickMap(); + if (m) { + const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_rect, point)); + return m_rect.m_rectangle.contains(crd) || m_borderGeometry.contains(m_rect.mapToItem(m_rect.quickMap(), point), + m_rect.border()->width(), + static_cast<const QGeoProjectionWebMercator&>(m_rect.map()->geoProjection())); + } else { + return true; + } + } + return false; + } + + QGeoMapPolygonGeometryOpenGL m_geometry; + QGeoMapPolylineGeometryOpenGL m_borderGeometry; + QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr; + MapPolygonNodeGL *m_node = nullptr; + MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QDECLARATIVERECTANGLEMAPITEM_P_P_H + diff --git a/src/location/declarativemaps/qgeomapitemgeometry.cpp b/src/location/declarativemaps/qgeomapitemgeometry.cpp index 2883c2bb..28e9fa67 100644 --- a/src/location/declarativemaps/qgeomapitemgeometry.cpp +++ b/src/location/declarativemaps/qgeomapitemgeometry.cpp @@ -48,6 +48,11 @@ QGeoMapItemGeometry::QGeoMapItemGeometry() { } +QGeoMapItemGeometry::~QGeoMapItemGeometry() +{ + +} + /*! \internal */ diff --git a/src/location/declarativemaps/qgeomapitemgeometry_p.h b/src/location/declarativemaps/qgeomapitemgeometry_p.h index ab81ff0b..aa5ea4ac 100644 --- a/src/location/declarativemaps/qgeomapitemgeometry_p.h +++ b/src/location/declarativemaps/qgeomapitemgeometry_p.h @@ -67,6 +67,7 @@ class Q_LOCATION_PRIVATE_EXPORT QGeoMapItemGeometry { public: QGeoMapItemGeometry(); + virtual ~QGeoMapItemGeometry(); inline bool isSourceDirty() const { return sourceDirty_; } inline bool isScreenDirty() const { return screenDirty_; } @@ -74,6 +75,7 @@ public: inline void markScreenDirty() { screenDirty_ = true; clipToViewport_ = true; } inline void markFullScreenDirty() { screenDirty_ = true; clipToViewport_ = false;} inline void markClean() { screenDirty_ = (sourceDirty_ = false); clipToViewport_ = true;} + inline void clearScreen() { screenDirty_ = false; } inline void setPreserveGeometry(bool value, const QGeoCoordinate &geoLeftBound = QGeoCoordinate()) { @@ -85,6 +87,7 @@ public: inline QRectF sourceBoundingBox() const { return sourceBounds_; } inline QRectF screenBoundingBox() const { return screenBounds_; } + inline void clearBounds() { sourceBounds_ = screenBounds_ = QRectF(); firstPointOffset_ = QPointF(); } inline QPointF firstPointOffset() const { return firstPointOffset_; } void translate(const QPointF &offset); @@ -128,6 +131,8 @@ public: static QRectF translateToCommonOrigin(const QList<QGeoMapItemGeometry *> &geoms); + mutable bool m_dataChanged = false; + private: QGeoMapItemGeometry(const QGeoMapItemGeometry &other); // Or else it may crash on copy QGeoMapItemGeometry &operator= (const QGeoMapItemGeometry & other); // Or else it may crash on copy diff --git a/src/location/labs/qsg/qmapcircleobjectqsg.cpp b/src/location/labs/qsg/qmapcircleobjectqsg.cpp index 32f3030b..f79be136 100644 --- a/src/location/labs/qsg/qmapcircleobjectqsg.cpp +++ b/src/location/labs/qsg/qmapcircleobjectqsg.cpp @@ -66,7 +66,7 @@ void QMapCircleObjectPrivateQSG::updateCirclePath() { const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_map->geoProjection()); QList<QGeoCoordinate> path; - QDeclarativeCircleMapItem::calculatePeripheralPoints(path, center(), radius(), CircleSamples, m_leftBound); + QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, center(), radius(), CircleSamples, m_leftBound); m_circlePath.clear(); for (const QGeoCoordinate &c : path) m_circlePath << p.geoToMapProjection(c); @@ -86,7 +86,7 @@ void QMapCircleObjectPrivateQSG::updateGeometry() QList<QDoubleVector2D> circlePath = m_circlePath; int pathCount = circlePath.size(); - bool preserve = QDeclarativeCircleMapItem::preserveCircleGeometry(circlePath, center(), radius(), p); + bool preserve = QDeclarativeCircleMapItemPrivateCPU::preserveCircleGeometry(circlePath, center(), radius(), p); // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); // to fix QTBUG-62154 m_geometry.markSourceDirty(); @@ -94,7 +94,7 @@ void QMapCircleObjectPrivateQSG::updateGeometry() m_geometry.setPreserveGeometry(preserve, m_leftBound); bool invertedCircle = false; - if (QDeclarativeCircleMapItem::crossEarthPole(center(), radius()) && circlePath.size() == pathCount) { + if (QDeclarativeCircleMapItemPrivateCPU::crossEarthPole(center(), radius()) && circlePath.size() == pathCount) { m_geometry.updateScreenPointsInvert(circlePath, *m_map); // invert fill area for really huge circles invertedCircle = true; } else { diff --git a/src/location/labs/qsg/qmapcircleobjectqsg_p_p.h b/src/location/labs/qsg/qmapcircleobjectqsg_p_p.h index 4e8162fa..6bbeb397 100644 --- a/src/location/labs/qsg/qmapcircleobjectqsg_p_p.h +++ b/src/location/labs/qsg/qmapcircleobjectqsg_p_p.h @@ -50,7 +50,7 @@ #include <QtLocation/private/qlocationglobal_p.h> #include <QtLocation/private/qgeomapobject_p_p.h> -#include <QtLocation/private/qdeclarativecirclemapitem_p.h> +#include <QtLocation/private/qdeclarativecirclemapitem_p_p.h> #include <QtLocation/private/qdeclarativepolygonmapitem_p.h> #include <QtLocation/private/qmapcircleobject_p.h> #include <QtLocation/private/qmapcircleobject_p_p.h> diff --git a/src/location/labs/qsg/qmappolygonobjectqsg_p_p.h b/src/location/labs/qsg/qmappolygonobjectqsg_p_p.h index 0f42a92e..8e6ae8a8 100644 --- a/src/location/labs/qsg/qmappolygonobjectqsg_p_p.h +++ b/src/location/labs/qsg/qmappolygonobjectqsg_p_p.h @@ -52,7 +52,7 @@ #include <QtLocation/private/qmappolygonobject_p.h> #include <QtLocation/private/qmappolygonobject_p_p.h> #include <QtLocation/private/qqsgmapobject_p.h> -#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p_p.h> #include <QtCore/qscopedvaluerollback.h> QT_BEGIN_NAMESPACE diff --git a/src/location/labs/qsg/qqsgmapobject_p.h b/src/location/labs/qsg/qqsgmapobject_p.h index 52036805..0a717e62 100644 --- a/src/location/labs/qsg/qqsgmapobject_p.h +++ b/src/location/labs/qsg/qqsgmapobject_p.h @@ -51,7 +51,7 @@ #include <QtLocation/private/qlocationglobal_p.h> #include <QtQuick/QSGOpacityNode> #include <QtLocation/private/qgeomapobject_p.h> -#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p_p.h> QT_BEGIN_NAMESPACE diff --git a/src/location/maps/qgeomap.cpp b/src/location/maps/qgeomap.cpp index dc8aa2c8..51af9199 100644 --- a/src/location/maps/qgeomap.cpp +++ b/src/location/maps/qgeomap.cpp @@ -41,6 +41,7 @@ #include "qdeclarativegeomapitembase_p.h" #include "qgeomapobject_p.h" #include "qgeomapobject_p_p.h" +#include <QtQuick/private/qquickitem_p.h> #include <QDebug> #include <QRectF> @@ -310,6 +311,12 @@ QList<QObject *> QGeoMap::mapObjectsAt(const QGeoCoordinate &/*coordinate*/) con return QList<QObject *>(); } +void QGeoMap::setItemToWindowTransform(const QTransform &itemToWindowTransform) +{ + Q_D(QGeoMap); + d->m_geoProjection->setItemToWindowTransform(itemToWindowTransform); +} + void QGeoMap::setVisibleArea(const QRectF &visibleArea) { Q_D(QGeoMap); diff --git a/src/location/maps/qgeomap_p.h b/src/location/maps/qgeomap_p.h index 216c8b64..e955b513 100644 --- a/src/location/maps/qgeomap_p.h +++ b/src/location/maps/qgeomap_p.h @@ -55,6 +55,7 @@ #include <QtPositioning/private/qdoublevector2d_p.h> #include <QtLocation/private/qgeoprojection_p.h> #include <QtLocation/qgeoroute.h> +#include <QTransform> QT_BEGIN_NAMESPACE @@ -67,6 +68,7 @@ class QQuickWindow; class QGeoMapParameter; class QDeclarativeGeoMapItemBase; class QGeoMapObject; +class QDeclarativeGeoMap; class Q_LOCATION_PRIVATE_EXPORT QGeoMap : public QObject { @@ -155,6 +157,7 @@ public: virtual void setCopyrightVisible(bool visible); virtual void removeMapObject(QGeoMapObject *obj); virtual QList<QObject *> mapObjectsAt(const QGeoCoordinate &coordinate) const; + virtual void setItemToWindowTransform(const QTransform &itemToWindowTransform); void setVisibleArea(const QRectF &visibleArea); QRectF visibleArea() const; diff --git a/src/location/maps/qgeoprojection.cpp b/src/location/maps/qgeoprojection.cpp index f64060e2..ff6a0b77 100644 --- a/src/location/maps/qgeoprojection.cpp +++ b/src/location/maps/qgeoprojection.cpp @@ -118,6 +118,19 @@ bool QGeoProjection::setBearing(qreal bearing, const QGeoCoordinate &coordinate) return false; } +void QGeoProjection::setItemToWindowTransform(const QTransform &itemToWindowTransform) +{ + if (m_itemToWindowTransform == itemToWindowTransform) + return; + m_qsgTransformDirty = true; + m_itemToWindowTransform = itemToWindowTransform; +} + +QTransform QGeoProjection::itemToWindowTransform() const +{ + return m_itemToWindowTransform; +} + /* * QGeoProjectionWebMercator implementation @@ -188,6 +201,31 @@ double QGeoProjectionWebMercator::minimumZoom() const return m_minimumZoom; } +QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation() const +{ + return toMatrix4x4(m_transformation); +} + +QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation_centered() const +{ + return toMatrix4x4(m_transformation0); +} + +const QMatrix4x4 &QGeoProjectionWebMercator::qsgTransform() const +{ + if (m_qsgTransformDirty) { + m_qsgTransformDirty = false; + m_qsgTransform = QMatrix4x4(m_itemToWindowTransform) * toMatrix4x4(m_transformation0); +// qDebug() << "QGeoProjectionWebMercator::qsgTransform" << m_itemToWindowTransform << toMatrix4x4(m_transformation0); + } + return m_qsgTransform; +} + +QDoubleVector3D QGeoProjectionWebMercator::centerMercator() const +{ + return geoToMapProjection(m_cameraData.center()).toVector3D(); +} + // This method recalculates the "no-trespassing" limits for the map center. // This has to be used when: // 1) the map is resized, because the meters per pixel remain the same, but @@ -273,19 +311,23 @@ QGeoCoordinate QGeoProjectionWebMercator::mapProjectionToGeo(const QDoubleVector return QWebMercator::mercatorToCoord(projection); } -//wraps around center -QDoubleVector2D QGeoProjectionWebMercator::wrapMapProjection(const QDoubleVector2D &projection) const +int QGeoProjectionWebMercator::projectionWrapFactor(const QDoubleVector2D &projection) const { - double x = projection.x(); + const double &x = projection.x(); if (m_cameraCenterXMercator < 0.5) { if (x - m_cameraCenterXMercator > 0.5 ) - x -= 1.0; + return -1; } else if (m_cameraCenterXMercator > 0.5) { if (x - m_cameraCenterXMercator < -0.5 ) - x += 1.0; + return 1; } + return 0; +} - return QDoubleVector2D(x, projection.y()); +//wraps around center +QDoubleVector2D QGeoProjectionWebMercator::wrapMapProjection(const QDoubleVector2D &projection) const +{ + return QDoubleVector2D(projection.x() + double(projectionWrapFactor(projection)), projection.y()); } QDoubleVector2D QGeoProjectionWebMercator::unwrapMapProjection(const QDoubleVector2D &wrappedProjection) const @@ -547,6 +589,7 @@ QGeoProjection::ProjectionType QGeoProjectionWebMercator::projectionType() const void QGeoProjectionWebMercator::setupCamera() { + m_qsgTransformDirty = true; m_centerMercator = geoToMapProjection(m_cameraData.center()); m_cameraCenterXMercator = m_centerMercator.x(); m_cameraCenterYMercator = m_centerMercator.y(); @@ -571,6 +614,10 @@ void QGeoProjectionWebMercator::setupCamera() // And in mercator space m_eyeMercator = m_centerMercator; m_eyeMercator.setZ(altitude_mercator / m_aperture); + m_eyeMercator0 = QDoubleVector3D(0,0,0); + m_eyeMercator0.setZ(altitude_mercator / m_aperture); + QDoubleVector3D eye0(0,0,0); + eye0.setZ(altitude * defaultTileSize / m_aperture); m_view = m_eye - m_center; QDoubleVector3D side = QDoubleVector3D::normal(m_view, QDoubleVector3D(0.0, 1.0, 0.0)); @@ -599,11 +646,13 @@ void QGeoProjectionWebMercator::setupCamera() QDoubleMatrix4x4 mTilt; mTilt.rotate(-m_cameraData.tilt(), m_side); m_eye = mTilt * m_view + m_center; + eye0 = mTilt * m_view; // In mercator space too QDoubleMatrix4x4 mTiltMercator; mTiltMercator.rotate(-m_cameraData.tilt(), m_sideMercator); m_eyeMercator = mTiltMercator * m_viewMercator + m_centerMercator; + m_eyeMercator0 = mTiltMercator * m_viewMercator; } m_view = m_eye - m_center; // ToDo: this should be inverted (center - eye), and the rest should follow @@ -634,8 +683,10 @@ void QGeoProjectionWebMercator::setupCamera() double verticalHalfFOV = QLocationUtils::degrees(atan(m_aperture)); - QDoubleMatrix4x4 cameraMatrix; - cameraMatrix.lookAt(m_eye, m_center, m_up); + m_cameraMatrix.setToIdentity(); + m_cameraMatrix.lookAt(m_eye, m_center, m_up); + m_cameraMatrix0.setToIdentity(); + m_cameraMatrix0.lookAt(eye0, QDoubleVector3D(0,0,0), m_up); QDoubleMatrix4x4 projectionMatrix; projectionMatrix.frustum(-m_halfWidth, m_halfWidth, -m_halfHeight, m_halfHeight, m_nearPlane, m_farPlane); @@ -656,10 +707,13 @@ void QGeoProjectionWebMercator::setupCamera() matScreenTransformation(0,3) = (0.5 + offsetPct.x()) * m_viewportWidth; matScreenTransformation(1,3) = (0.5 + offsetPct.y()) * m_viewportHeight; - m_transformation = matScreenTransformation * projectionMatrix * cameraMatrix; + m_transformation = matScreenTransformation * projectionMatrix * m_cameraMatrix; m_quickItemTransformation = m_transformation; m_transformation.scale(m_sideLengthPixels, m_sideLengthPixels, 1.0); + m_transformation0 = matScreenTransformation * projectionMatrix * m_cameraMatrix0; + m_transformation0.scale(m_sideLengthPixels, m_sideLengthPixels, 1.0); + m_centerNearPlane = m_eye - m_viewNormalized; m_centerNearPlaneMercator = m_eyeMercator - m_viewNormalized * m_nearPlaneMercator; diff --git a/src/location/maps/qgeoprojection_p.h b/src/location/maps/qgeoprojection_p.h index 2e1af8c5..9a75246a 100644 --- a/src/location/maps/qgeoprojection_p.h +++ b/src/location/maps/qgeoprojection_p.h @@ -52,6 +52,8 @@ #include <QtLocation/private/qgeocameradata_p.h> #include <QtPositioning/private/qdoublematrix4x4_p.h> #include <QtPositioning/QGeoShape> +#include <QMatrix4x4> +#include <QTransform> QT_BEGIN_NAMESPACE @@ -107,6 +109,17 @@ public: virtual QGeoShape visibleRegion() const; virtual bool setBearing(qreal bearing, const QGeoCoordinate &coordinate); + virtual QMatrix4x4 projectionTransformation() const = 0; // This brings a mercator coord into the correct viewport coordinate. + virtual QMatrix4x4 projectionTransformation_centered() const = 0; // Same as projectionTransformation, but the center of the camera is around 0,0. + // Requires subsequent shifting of the geometry to fit such camera. + virtual const QMatrix4x4 &qsgTransform() const = 0; + virtual QDoubleVector3D centerMercator() const = 0; + + void setItemToWindowTransform(const QTransform &itemToWindowTransform); + virtual QTransform itemToWindowTransform() const; + + QTransform m_itemToWindowTransform; + mutable bool m_qsgTransformDirty = true; }; class Q_LOCATION_PRIVATE_EXPORT QGeoProjectionWebMercator : public QGeoProjection @@ -117,6 +130,11 @@ public: // From QGeoProjection double minimumZoom() const override; + QMatrix4x4 projectionTransformation() const override; + QMatrix4x4 projectionTransformation_centered() const override; + const QMatrix4x4 &qsgTransform() const override; + QDoubleVector3D centerMercator() const override; + double maximumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const override; double minimumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const override; @@ -144,6 +162,7 @@ public: QDoubleVector2D geoToMapProjection(const QGeoCoordinate &coordinate) const; QGeoCoordinate mapProjectionToGeo(const QDoubleVector2D &projection) const; + int projectionWrapFactor(const QDoubleVector2D &projection) const; QDoubleVector2D wrapMapProjection(const QDoubleVector2D &projection) const; QDoubleVector2D unwrapMapProjection(const QDoubleVector2D &wrappedProjection) const; @@ -213,7 +232,10 @@ protected: double m_1_viewportWidth; double m_1_viewportHeight; + QDoubleMatrix4x4 m_cameraMatrix; + QDoubleMatrix4x4 m_cameraMatrix0; QDoubleMatrix4x4 m_transformation; + QDoubleMatrix4x4 m_transformation0; QDoubleMatrix4x4 m_quickItemTransformation; QDoubleVector3D m_eye; QDoubleVector3D m_up; @@ -234,6 +256,7 @@ protected: // For the clipping region QDoubleVector3D m_centerMercator; QDoubleVector3D m_eyeMercator; + QDoubleVector3D m_eyeMercator0; QDoubleVector3D m_viewMercator; QDoubleVector3D m_upMercator; QDoubleVector3D m_sideMercator; @@ -245,6 +268,8 @@ protected: QList<QDoubleVector2D> m_visibleRegionExpanded; QList<QDoubleVector2D> m_projectableRegion; bool m_visibleRegionDirty; + + mutable QMatrix4x4 m_qsgTransform; QRectF m_visibleArea; Q_DISABLE_COPY(QGeoProjectionWebMercator) diff --git a/src/location/maps/qgeotiledmap.cpp b/src/location/maps/qgeotiledmap.cpp index 74346fdb..e6c91042 100644 --- a/src/location/maps/qgeotiledmap.cpp +++ b/src/location/maps/qgeotiledmap.cpp @@ -329,7 +329,7 @@ void QGeoTiledMapPrivate::changeCameraData(const QGeoCameraData &cameraData) m_mapScene->setCameraData(cam); updateScene(); - q->sgNodeChanged(); + q->sgNodeChanged(); // ToDo: explain why emitting twice } void QGeoTiledMapPrivate::updateScene() @@ -371,7 +371,7 @@ void QGeoTiledMapPrivate::setVisibleArea(const QRectF &visibleArea) if (m_copyrightVisible) q->evaluateCopyrights(m_mapScene->visibleTiles()); updateScene(); - q->sgNodeChanged(); + q->sgNodeChanged(); // ToDo: explain why emitting twice } QRectF QGeoTiledMapPrivate::visibleArea() const |