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 }