File indexing completed on 2024-05-19 03:51:40

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
0004 //
0005 
0006 #include "BuildingGraphicsItem.h"
0007 
0008 #include "MarbleDebug.h"
0009 #include "ViewportParams.h"
0010 #include "GeoDataPlacemark.h"
0011 #include "GeoDataLinearRing.h"
0012 #include "GeoDataPolygon.h"
0013 #include "GeoDataBuilding.h"
0014 #include "GeoDataMultiGeometry.h"
0015 #include "GeoDataPolyStyle.h"
0016 #include "OsmPlacemarkData.h"
0017 #include "GeoPainter.h"
0018 
0019 #include <QScreen>
0020 #include <QApplication>
0021 
0022 namespace Marble
0023 {
0024 
0025 BuildingGraphicsItem::BuildingGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building)
0026     : AbstractGeoPolygonGraphicsItem(placemark, building)
0027 {
0028     if (const auto ring = geodata_cast<GeoDataLinearRing>(&building->multiGeometry()->at(0))) {
0029         setLinearRing(ring);
0030     } else if (const auto poly = geodata_cast<GeoDataPolygon>(&building->multiGeometry()->at(0))) {
0031         setPolygon(poly);
0032     }
0033 
0034     setZValue(building->height());
0035     Q_ASSERT(building->height() > 0.0);
0036 
0037     QStringList paintLayers;
0038     paintLayers << QStringLiteral("Polygon/Building/frame")
0039                 << QStringLiteral("Polygon/Building/roof");
0040     setPaintLayers(paintLayers);
0041 }
0042 
0043 BuildingGraphicsItem::~BuildingGraphicsItem()
0044 {
0045     qDeleteAll(m_cachedOuterPolygons);
0046     qDeleteAll(m_cachedInnerPolygons);
0047     qDeleteAll(m_cachedOuterRoofPolygons);
0048     qDeleteAll(m_cachedInnerRoofPolygons);
0049 }
0050 
0051 void BuildingGraphicsItem::initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport,
0052                                                       bool &drawAccurate3D, bool &isCameraAboveBuilding ) const
0053 {
0054     drawAccurate3D = false;
0055     isCameraAboveBuilding = false;
0056 
0057     auto const screen = QApplication::screens().first();
0058     double const physicalSize = 1.0; // mm
0059     int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM));
0060 
0061     QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding);
0062     qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) );
0063     drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize;
0064 }
0065 
0066 // backface culling for the perspective drawing below assumes all polygons in clockwise order
0067 static void normalizeWindingOrder(QPolygonF *polygon)
0068 {
0069     double c = 0;
0070     for (int i = 0; i <polygon->size(); ++i) {
0071         c += (polygon->at((i + 1) % polygon->size()).x() - polygon->at(i).x()) * (polygon->at(i).y() + polygon->at((i + 1) % polygon->size()).y());
0072     }
0073     if (c < 0) {
0074         std::reverse(polygon->begin(), polygon->end());
0075     }
0076 }
0077 
0078 void BuildingGraphicsItem::updatePolygons(const ViewportParams &viewport,
0079                                           QVector<QPolygonF*>& outerPolygons,
0080                                           QVector<QPolygonF*>& innerPolygons,
0081                                           bool &hasInnerBoundaries) const
0082 {
0083     // Since subtracting one fully contained polygon from another results in a single
0084     // polygon with a "connecting line" between the inner and outer part we need
0085     // to first paint the inner area with no pen and then the outlines with the correct pen.
0086     hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().isEmpty() : false;
0087     if (polygon()) {
0088         if (hasInnerBoundaries) {
0089             screenPolygons(viewport, polygon(), innerPolygons, outerPolygons);
0090         }
0091         else {
0092             viewport.screenCoordinates(polygon()->outerBoundary(), outerPolygons);
0093         }
0094     } else if (ring()) {
0095         viewport.screenCoordinates(*ring(), outerPolygons);
0096     }
0097     for (auto *polygon : outerPolygons) {
0098         normalizeWindingOrder(polygon);
0099     }
0100     for (auto *polygon : innerPolygons) {
0101         normalizeWindingOrder(polygon);
0102     }
0103 }
0104 
0105 QPointF BuildingGraphicsItem::centroid(const QPolygonF &polygon, double &area)
0106 {
0107     auto centroid = QPointF(0.0, 0.0);
0108     area = 0.0;
0109     for (auto i=0, n=polygon.size(); i<n; ++i) {
0110         auto const x0 = polygon[i].x();
0111         auto const y0 = polygon[i].y();
0112         auto const j = i == n-1 ? 0 : i+1;
0113         auto const x1 = polygon[j].x();
0114         auto const y1 = polygon[j].y();
0115         auto const a = x0*y1 - x1*y0;
0116         area += a;
0117         centroid.rx() += (x0 + x1)*a;
0118         centroid.ry() += (y0 + y1)*a;
0119     }
0120 
0121     area *= 0.5;
0122     return area != 0 ? centroid / (6.0*area) : polygon.boundingRect().center();
0123 }
0124 
0125 QPointF BuildingGraphicsItem::buildingOffset(const QPointF &point, const ViewportParams *viewport, bool* isCameraAboveBuilding) const
0126 {
0127     qreal const cameraFactor = 0.5 * tan(0.5 * 110 * DEG2RAD);
0128     Q_ASSERT(building()->height() > 0.0);
0129     qreal const buildingFactor = building()->height() / EARTH_RADIUS;
0130 
0131     qreal const cameraHeightPixel = viewport->width() * cameraFactor;
0132     qreal buildingHeightPixel = viewport->radius() * buildingFactor;
0133     qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel;
0134 
0135     if (isCameraAboveBuilding) {
0136         *isCameraAboveBuilding = cameraDistance > 0;
0137     }
0138 
0139     qreal const cc = cameraDistance * cameraHeightPixel;
0140     qreal const cb = cameraDistance * buildingHeightPixel;
0141 
0142     // The following lines calculate the same result, but are potentially slower due
0143     // to using more trigonometric method calls
0144     // qreal const alpha1 = atan2(offsetX, cameraHeightPixel);
0145     // qreal const alpha2 = atan2(offsetX, cameraHeightPixel-buildingHeightPixel);
0146     // qreal const shiftX = 2 * (cameraHeightPixel-buildingHeightPixel) * sin(0.5*(alpha2-alpha1));
0147 
0148     qreal const offsetX = point.x() - viewport->width() / 2.0;
0149     qreal const offsetY = point.y() - viewport->height() / 2.0;
0150 
0151     qreal const shiftX = offsetX * cb / (cc + offsetX);
0152     qreal const shiftY = offsetY * cb / (cc + offsetY);
0153 
0154     return QPointF(shiftX, shiftY);
0155 }
0156 
0157 void BuildingGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel)
0158 {
0159     // Just display flat buildings for tile level 17
0160     if (tileZoomLevel == 17) {
0161         setZValue(0.0);
0162         if (layer.endsWith(QLatin1String("/roof"))) {
0163             AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel );
0164         }
0165         return;
0166     }
0167     setZValue(building()->height());
0168 
0169     // For level 18, 19 .. render 3D buildings in perspective
0170     if (layer.endsWith(QLatin1String("/frame"))) {
0171         qDeleteAll(m_cachedOuterPolygons);
0172         qDeleteAll(m_cachedInnerPolygons);
0173         qDeleteAll(m_cachedOuterRoofPolygons);
0174         qDeleteAll(m_cachedInnerRoofPolygons);
0175         m_cachedOuterPolygons.clear();
0176         m_cachedInnerPolygons.clear();
0177         m_cachedOuterRoofPolygons.clear();
0178         m_cachedInnerRoofPolygons.clear();
0179         updatePolygons(*viewport, m_cachedOuterPolygons,
0180                                  m_cachedInnerPolygons,
0181                                  m_hasInnerBoundaries);
0182         if (m_cachedOuterPolygons.isEmpty()) {
0183             return;
0184         }
0185         paintFrame(painter, viewport);
0186     } else if (layer.endsWith(QLatin1String("/roof"))) {
0187         if (m_cachedOuterPolygons.isEmpty()) {
0188             return;
0189         }
0190         paintRoof(painter, viewport);
0191     } else {
0192         mDebug() << "Didn't expect to have to paint layer " << layer << ", ignoring it.";
0193     }
0194 }
0195 
0196 void BuildingGraphicsItem::paintRoof(GeoPainter* painter, const ViewportParams* viewport)
0197 {
0198     bool drawAccurate3D;
0199     bool isCameraAboveBuilding;
0200     initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
0201     if (!isCameraAboveBuilding) {
0202         return; // do not render roof if we look inside the building
0203     }
0204 
0205     bool isValid = true;
0206     if (s_previousStyle != style().data()) {
0207         isValid = configurePainter(painter, *viewport);
0208 
0209         QFont font = painter->font(); // TODO: better font configuration
0210         if (font.pointSize() != 10) {
0211             font.setPointSize( 10 );
0212             painter->setFont(font);
0213         }
0214     }
0215     s_previousStyle = style().data();
0216 
0217     if (!isValid) return;
0218 
0219     // first paint the area (and the outline if there are no inner boundaries)
0220 
0221     if ( drawAccurate3D) {
0222         if (m_hasInnerBoundaries) {
0223 
0224             QPen const currentPen = painter->pen();
0225 
0226             painter->setPen(Qt::NoPen);
0227             QVector<QPolygonF*> fillPolygons =
0228                     painter->createFillPolygons( m_cachedOuterRoofPolygons,
0229                                                  m_cachedInnerRoofPolygons );
0230 
0231             for( const QPolygonF* fillPolygon: fillPolygons ) {
0232                 painter->drawPolygon(*fillPolygon);
0233             }
0234 
0235             painter->setPen(currentPen);
0236 
0237             for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
0238                 painter->drawPolyline( *outerRoof );
0239             }
0240             for( const QPolygonF* innerRoof: m_cachedInnerRoofPolygons ) {
0241                 painter->drawPolyline( *innerRoof );
0242             }
0243             qDeleteAll(fillPolygons);
0244         }
0245         else {
0246             for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
0247                 painter->drawPolygon( *outerRoof );
0248             }
0249         }
0250     }
0251     else {
0252         QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().center(), viewport);
0253         painter->translate(offset);
0254 
0255         if (m_hasInnerBoundaries) {
0256 
0257             QPen const currentPen = painter->pen();
0258 
0259             painter->setPen(Qt::NoPen);
0260             QVector<QPolygonF*> fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons,
0261                                                                    m_cachedInnerPolygons );
0262 
0263             for( const QPolygonF* fillPolygon: fillPolygons ) {
0264                 painter->drawPolygon(*fillPolygon);
0265             }
0266 
0267             painter->setPen(currentPen);
0268 
0269             for( const QPolygonF* outerPolygon:  m_cachedOuterPolygons ) {
0270                 painter->drawPolyline( *outerPolygon );
0271             }
0272             for( const QPolygonF* innerPolygon:  m_cachedInnerPolygons ) {
0273                 painter->drawPolyline( *innerPolygon );
0274             }
0275             qDeleteAll(fillPolygons);
0276         }
0277         else {
0278             for( const QPolygonF* outerPolygon:  m_cachedOuterPolygons ) {
0279                 painter->drawPolygon( *outerPolygon );
0280             }
0281         }
0282         painter->translate(-offset);
0283 
0284     }
0285 
0286 
0287     qreal maxSize(0.0);
0288     double maxArea = 0.0;
0289 
0290     for (int i = 0; i < m_cachedOuterRoofPolygons.size(); ++i) {
0291         const QPolygonF *outerRoof = m_cachedOuterRoofPolygons[i];
0292 
0293         QPointF roofCenter;
0294 
0295         // Label position calculation
0296         if (!building()->name().isEmpty() || !building()->entries().isEmpty()) {
0297             QSizeF const polygonSize = outerRoof->boundingRect().size();
0298             qreal size = polygonSize.width() * polygonSize.height();
0299             if (size > maxSize) {
0300                 maxSize = size;
0301                 double area;
0302                 roofCenter = centroid(*outerRoof, area);
0303                 maxArea = qMax(area, maxArea);
0304             }
0305         }
0306 
0307         // Draw the housenumber labels
0308         if (drawAccurate3D && !building()->name().isEmpty() && !roofCenter.isNull()) {
0309             double const w2 = 0.5 * painter->fontMetrics().horizontalAdvance(building()->name());
0310             double const ascent = painter->fontMetrics().ascent();
0311             double const descent = painter->fontMetrics().descent();
0312             double const a2 = 0.5 * painter->fontMetrics().ascent();
0313             QPointF const textPosition = roofCenter - QPointF(w2, -a2);
0314             if (outerRoof->containsPoint(textPosition + QPointF(-2, -ascent), Qt::OddEvenFill)
0315                     && outerRoof->containsPoint(textPosition + QPointF(-2, descent), Qt::OddEvenFill)
0316                     && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, descent), Qt::OddEvenFill)
0317                     && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, -ascent), Qt::OddEvenFill)
0318                     ) {
0319                 painter->drawTextFragment(roofCenter.toPoint(), building()->name(),
0320                                          painter->font().pointSize(), painter->brush().color());
0321             }
0322         }
0323     }
0324 
0325     // Render additional housenumbers at building entries
0326     if (!building()->entries().isEmpty() && maxArea > 1600 * building()->entries().size()) {
0327         for(const auto &entry: building()->entries()) {
0328             qreal x, y;
0329             viewport->screenCoordinates(entry.point, x, y);
0330             QPointF point(x, y);
0331             point += buildingOffset(point, viewport);
0332             painter->drawTextFragment(point.toPoint(),
0333                                      building()->name(), painter->font().pointSize(), painter->brush().color(),
0334                                      GeoPainter::RoundFrame);
0335         }
0336     }
0337 }
0338 
0339 void BuildingGraphicsItem::paintFrame(GeoPainter *painter, const ViewportParams *viewport)
0340 {
0341     // TODO: how does this match the Q_ASSERT in the constructor?
0342     if (building()->height() == 0.0) {
0343         return;
0344     }
0345 
0346     if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4))
0347             || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) {
0348         return;
0349     }
0350 
0351     bool drawAccurate3D;
0352     bool isCameraAboveBuilding;
0353     initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
0354 
0355     bool isValid = true;
0356     if (s_previousStyle != style().data()) {
0357         isValid = configurePainterForFrame(painter);
0358     }
0359     s_previousStyle = style().data();
0360 
0361     if (!isValid) return;
0362 
0363     if ( drawAccurate3D && isCameraAboveBuilding ) {
0364         for (const QPolygonF *outline: m_cachedOuterPolygons) {
0365             if (outline->isEmpty()) {
0366                 continue;
0367             }
0368             // draw the building sides
0369             int const size = outline->size();
0370             QPolygonF * outerRoof = new QPolygonF;
0371             outerRoof->reserve(outline->size());
0372             QPointF a = (*outline)[0];
0373             QPointF shiftA = a + buildingOffset(a, viewport);
0374             outerRoof->append(shiftA);
0375             for (int i=1; i<size; ++i) {
0376                 QPointF const & b = (*outline)[i];
0377                 QPointF const shiftB = b + buildingOffset(b, viewport);
0378                 // perform backface culling
0379                 bool backface = (b.x() - a.x()) * (shiftA.y() - a.y())
0380                         - (b.y() - a.y()) * (shiftA.x() - a.x()) >= 0;
0381                 if (!backface) {
0382                     QPolygonF buildingSide;
0383                     buildingSide.reserve(4);
0384                     buildingSide << a << shiftA << shiftB << b;
0385                     painter->drawPolygon(buildingSide);
0386                 }
0387                 a = b;
0388                 shiftA = shiftB;
0389                 outerRoof->append(shiftA);
0390             }
0391             m_cachedOuterRoofPolygons.append(outerRoof);
0392         }
0393         for (const QPolygonF *outline: m_cachedInnerPolygons) {
0394             if (outline->isEmpty()) {
0395                 continue;
0396             }
0397             // draw the building sides
0398             int const size = outline->size();
0399             QPolygonF * innerRoof = new QPolygonF;
0400             innerRoof->reserve(outline->size());
0401             QPointF a = (*outline)[0];
0402             QPointF shiftA = a + buildingOffset(a, viewport);
0403             innerRoof->append(shiftA);
0404             for (int i=1; i<size; ++i) {
0405                 QPointF const & b = (*outline)[i];
0406                 QPointF const shiftB = b + buildingOffset(b, viewport);
0407                 // perform backface culling
0408                 bool backface = (b.x() - a.x()) * (shiftA.y() - a.y())
0409                         - (b.y() - a.y()) * (shiftA.x() - a.x()) >= 0;
0410                 if (backface) {
0411                     QPolygonF buildingSide;
0412                     buildingSide.reserve(4);
0413                     buildingSide << a << shiftA << shiftB << b;
0414                     painter->drawPolygon(buildingSide);
0415                 }
0416                 a = b;
0417                 shiftA = shiftB;
0418                 innerRoof->append(shiftA);
0419             }
0420             m_cachedInnerRoofPolygons.append(innerRoof);
0421         }
0422     } else {
0423             // don't draw the building sides - just draw the base frame instead
0424             QVector<QPolygonF*> fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons,
0425                                                                    m_cachedInnerPolygons );
0426 
0427             for( QPolygonF* fillPolygon: fillPolygons ) {
0428                 painter->drawPolygon(*fillPolygon);
0429             }
0430             qDeleteAll(fillPolygons);
0431     }
0432 }
0433 
0434 void BuildingGraphicsItem::screenPolygons(const ViewportParams &viewport, const GeoDataPolygon *polygon,
0435                                                     QVector<QPolygonF *> &innerPolygons,
0436                                                     QVector<QPolygonF *> &outerPolygons
0437                                                     )
0438 {
0439     Q_ASSERT(polygon);
0440 
0441     viewport.screenCoordinates(polygon->outerBoundary(), outerPolygons);
0442 
0443     QVector<GeoDataLinearRing> const & innerBoundaries = polygon->innerBoundaries();
0444     for (const GeoDataLinearRing &innerBoundary: innerBoundaries) {
0445         QVector<QPolygonF*> innerPolygonsPerBoundary;
0446         viewport.screenCoordinates(innerBoundary, innerPolygonsPerBoundary);
0447 
0448         innerPolygons.reserve(innerPolygons.size() + innerPolygonsPerBoundary.size());
0449         for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) {
0450             innerPolygons << innerPolygonPerBoundary;
0451         }
0452     }
0453 }
0454 
0455 bool BuildingGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *viewport) const
0456 {
0457     if (m_cachedOuterPolygons.isEmpty()) {
0458         // Level 17
0459         return AbstractGeoPolygonGraphicsItem::contains(screenPosition, viewport);
0460     }
0461 
0462     QPointF const point = screenPosition;
0463     for (auto polygon: m_cachedOuterRoofPolygons) {
0464         if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0465             for (auto polygon: m_cachedInnerRoofPolygons) {
0466                 if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0467                     return false;
0468                 }
0469             }
0470             return true;
0471         }
0472     }
0473     for (auto polygon: m_cachedOuterPolygons) {
0474         if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0475             for (auto polygon: m_cachedInnerPolygons) {
0476                 if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0477                     return false;
0478                 }
0479             }
0480             return true;
0481         }
0482     }
0483     return false;
0484 }
0485 
0486 bool BuildingGraphicsItem::configurePainterForFrame(GeoPainter *painter) const
0487 {
0488     QPen currentPen = painter->pen();
0489 
0490     GeoDataStyle::ConstPtr style = this->style();
0491     if (!style) {
0492         painter->setPen( QPen() );
0493     }
0494     else {
0495         const GeoDataPolyStyle& polyStyle = style->polyStyle();
0496 
0497         if (currentPen.style() != Qt::NoPen) {
0498             painter->setPen(Qt::NoPen);
0499         }
0500 
0501         if (!polyStyle.fill()) {
0502             return false;
0503         }
0504         else {
0505             const QColor paintedColor = polyStyle.paintedColor().darker(150);
0506             if (painter->brush().color() != paintedColor) {
0507                 painter->setBrush(paintedColor);
0508             }
0509         }
0510     }
0511 
0512     return true;
0513 }
0514 
0515 }