6#include "BuildingGraphicsItem.h"
8#include "GeoDataBuilding.h"
9#include "GeoDataLinearRing.h"
10#include "GeoDataMultiGeometry.h"
11#include "GeoDataPlacemark.h"
12#include "GeoDataPolyStyle.h"
13#include "GeoDataPolygon.h"
14#include "GeoPainter.h"
15#include "MarbleDebug.h"
16#include "OsmPlacemarkData.h"
19#include <QApplication>
25BuildingGraphicsItem::BuildingGraphicsItem(
const GeoDataPlacemark *placemark,
const GeoDataBuilding *building)
26 : AbstractGeoPolygonGraphicsItem(placemark, building)
28 if (
const auto ring = geodata_cast<GeoDataLinearRing>(&building->multiGeometry()->at(0))) {
30 }
else if (
const auto poly = geodata_cast<GeoDataPolygon>(&building->multiGeometry()->at(0))) {
34 setZValue(building->height());
35 Q_ASSERT(building->height() > 0.0);
38 paintLayers << QStringLiteral(
"Polygon/Building/frame") << QStringLiteral(
"Polygon/Building/roof");
39 setPaintLayers(paintLayers);
42BuildingGraphicsItem::~BuildingGraphicsItem()
44 qDeleteAll(m_cachedOuterPolygons);
45 qDeleteAll(m_cachedInnerPolygons);
46 qDeleteAll(m_cachedOuterRoofPolygons);
47 qDeleteAll(m_cachedInnerRoofPolygons);
50void BuildingGraphicsItem::initializeBuildingPainting(
const GeoPainter *painter,
51 const ViewportParams *viewport,
53 bool &isCameraAboveBuilding)
const
55 drawAccurate3D =
false;
56 isCameraAboveBuilding =
false;
59 double const physicalSize = 1.0;
60 int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM));
62 QPointF offsetAtCorner = buildingOffset(
QPointF(0, 0), viewport, &isCameraAboveBuilding);
63 qreal maxOffset = qMax(qAbs(offsetAtCorner.
x()), qAbs(offsetAtCorner.
y()));
64 drawAccurate3D = painter->mapQuality() ==
HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize;
68static void normalizeWindingOrder(
QPolygonF *polygon)
71 for (
int i = 0; i < polygon->
size(); ++i) {
72 c += (polygon->
at((i + 1) % polygon->
size()).x() - polygon->
at(i).x()) * (polygon->
at(i).y() + polygon->
at((i + 1) % polygon->
size()).y());
75 std::reverse(polygon->
begin(), polygon->
end());
79void BuildingGraphicsItem::updatePolygons(
const ViewportParams &viewport,
82 bool &hasInnerBoundaries)
const
87 hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().
isEmpty() :
false;
89 if (hasInnerBoundaries) {
90 screenPolygons(viewport, polygon(), innerPolygons, outerPolygons);
92 viewport.screenCoordinates(polygon()->outerBoundary(), outerPolygons);
95 viewport.screenCoordinates(*ring(), outerPolygons);
97 for (
auto polygon : std::as_const(outerPolygons)) {
98 normalizeWindingOrder(polygon);
100 for (
auto polygon : std::as_const(innerPolygons)) {
101 normalizeWindingOrder(polygon);
105QPointF BuildingGraphicsItem::centroid(
const QPolygonF &polygon,
double &area)
107 auto centroid =
QPointF(0.0, 0.0);
109 for (qsizetype i = 0, n = polygon.
size(); i < n; ++i) {
110 auto const x0 = polygon[i].x();
111 auto const y0 = polygon[i].y();
112 auto const j = i == n - 1 ? 0 : i + 1;
113 auto const x1 = polygon[j].x();
114 auto const y1 = polygon[j].y();
115 auto const a = x0 * y1 - x1 * y0;
117 centroid.rx() += (x0 + x1) * a;
118 centroid.ry() += (y0 + y1) * a;
122 return area != 0 ? centroid / (6.0 * area) : polygon.boundingRect().
center();
125QPointF BuildingGraphicsItem::buildingOffset(
const QPointF &point,
const ViewportParams *viewport,
bool *isCameraAboveBuilding)
const
127 qreal
const cameraFactor = 0.5 * tan(0.5 * 110 * DEG2RAD);
128 Q_ASSERT(building()->height() > 0.0);
129 qreal
const buildingFactor = building()->height() / EARTH_RADIUS;
131 qreal
const cameraHeightPixel = viewport->width() * cameraFactor;
132 qreal buildingHeightPixel = viewport->radius() * buildingFactor;
133 qreal
const cameraDistance = cameraHeightPixel - buildingHeightPixel;
135 if (isCameraAboveBuilding) {
136 *isCameraAboveBuilding = cameraDistance > 0;
139 qreal
const cc = cameraDistance * cameraHeightPixel;
140 qreal
const cb = cameraDistance * buildingHeightPixel;
148 qreal
const offsetX = point.
x() - viewport->width() / 2.0;
149 qreal
const offsetY = point.
y() - viewport->height() / 2.0;
151 qreal
const shiftX = offsetX * cb / (cc + offsetX);
152 qreal
const shiftY = offsetY * cb / (cc + offsetY);
154 return {shiftX, shiftY};
157void BuildingGraphicsItem::paint(GeoPainter *painter,
const ViewportParams *viewport,
const QString &layer,
int tileZoomLevel)
160 if (tileZoomLevel == 17) {
163 AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel);
167 setZValue(building()->height());
171 qDeleteAll(m_cachedOuterPolygons);
172 qDeleteAll(m_cachedInnerPolygons);
173 qDeleteAll(m_cachedOuterRoofPolygons);
174 qDeleteAll(m_cachedInnerRoofPolygons);
175 m_cachedOuterPolygons.clear();
176 m_cachedInnerPolygons.clear();
177 m_cachedOuterRoofPolygons.clear();
178 m_cachedInnerRoofPolygons.clear();
179 updatePolygons(*viewport, m_cachedOuterPolygons, m_cachedInnerPolygons, m_hasInnerBoundaries);
180 if (m_cachedOuterPolygons.isEmpty()) {
183 paintFrame(painter, viewport);
185 if (m_cachedOuterPolygons.isEmpty()) {
188 paintRoof(painter, viewport);
190 mDebug() <<
"Didn't expect to have to paint layer " << layer <<
", ignoring it.";
194void BuildingGraphicsItem::paintRoof(GeoPainter *painter,
const ViewportParams *viewport)
197 bool isCameraAboveBuilding;
198 initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
199 if (!isCameraAboveBuilding) {
204 if (s_previousStyle != style().data()) {
205 isValid = configurePainter(painter, *viewport);
207 QFont font = painter->font();
210 painter->setFont(font);
213 s_previousStyle = style().data();
220 if (drawAccurate3D) {
221 if (m_hasInnerBoundaries) {
222 QPen const currentPen = painter->pen();
225 QList<QPolygonF *> fillPolygons = painter->createFillPolygons(m_cachedOuterRoofPolygons, m_cachedInnerRoofPolygons);
227 for (
const QPolygonF *fillPolygon : std::as_const(fillPolygons)) {
228 painter->drawPolygon(*fillPolygon);
231 painter->setPen(currentPen);
233 for (
const QPolygonF *outerRoof : std::as_const(m_cachedOuterRoofPolygons)) {
234 painter->drawPolyline(*outerRoof);
236 for (
const QPolygonF *innerRoof : std::as_const(m_cachedInnerRoofPolygons)) {
237 painter->drawPolyline(*innerRoof);
239 qDeleteAll(fillPolygons);
241 for (
const QPolygonF *outerRoof : std::as_const(m_cachedOuterRoofPolygons)) {
242 painter->drawPolygon(*outerRoof);
246 QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().
center(), viewport);
247 painter->translate(offset);
249 if (m_hasInnerBoundaries) {
250 QPen const currentPen = painter->pen();
253 QList<QPolygonF *> fillPolygons = painter->createFillPolygons(m_cachedOuterPolygons, m_cachedInnerPolygons);
255 for (
const QPolygonF *fillPolygon : std::as_const(fillPolygons)) {
256 painter->drawPolygon(*fillPolygon);
259 painter->setPen(currentPen);
261 for (
const QPolygonF *outerPolygon : std::as_const(m_cachedOuterPolygons)) {
262 painter->drawPolyline(*outerPolygon);
264 for (
const QPolygonF *innerPolygon : std::as_const(m_cachedInnerPolygons)) {
265 painter->drawPolyline(*innerPolygon);
267 qDeleteAll(fillPolygons);
269 for (
const QPolygonF *outerPolygon : std::as_const(m_cachedOuterPolygons)) {
270 painter->drawPolygon(*outerPolygon);
273 painter->translate(-offset);
277 double maxArea = 0.0;
279 for (
int i = 0; i < m_cachedOuterRoofPolygons.size(); ++i) {
280 const QPolygonF *outerRoof = m_cachedOuterRoofPolygons[i];
285 if (!building()->
name().isEmpty() || !building()->entries().isEmpty()) {
287 qreal size = polygonSize.
width() * polygonSize.
height();
288 if (size > maxSize) {
291 roofCenter = centroid(*outerRoof, area);
292 maxArea = qMax(area, maxArea);
297 if (drawAccurate3D && !building()->
name().isEmpty() && !roofCenter.
isNull()) {
298 double const w2 = 0.5 * painter->fontMetrics().horizontalAdvance(building()->
name());
299 double const ascent = painter->fontMetrics().ascent();
300 double const descent = painter->fontMetrics().descent();
301 double const a2 = 0.5 * painter->fontMetrics().ascent();
307 painter->drawTextFragment(roofCenter.
toPoint(), building()->
name(), painter->font().pointSize(), painter->brush().color());
313 if (!building()->entries().isEmpty() && maxArea > 1600 * building()->entries().size()) {
314 for (
const auto &entry : building()->entries()) {
316 viewport->screenCoordinates(entry.point, x, y);
318 point += buildingOffset(point, viewport);
319 painter->drawTextFragment(point.
toPoint(), building()->
name(), painter->font().pointSize(), painter->brush().color(), GeoPainter::RoundFrame);
324void BuildingGraphicsItem::paintFrame(GeoPainter *painter,
const ViewportParams *viewport)
327 if (building()->height() == 0.0) {
331 if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4)) || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) {
336 bool isCameraAboveBuilding;
337 initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
340 if (s_previousStyle != style().data()) {
341 isValid = configurePainterForFrame(painter);
343 s_previousStyle = style().data();
348 if (drawAccurate3D && isCameraAboveBuilding) {
349 for (
const QPolygonF *outline : std::as_const(m_cachedOuterPolygons)) {
350 if (outline->isEmpty()) {
354 int const size = outline->size();
356 outerRoof->
reserve(outline->size());
358 QPointF shiftA = a + buildingOffset(a, viewport);
359 outerRoof->
append(shiftA);
360 for (
int i = 1; i < size; ++i) {
361 QPointF const &b = (*outline)[i];
362 QPointF const shiftB = b + buildingOffset(b, viewport);
364 bool backface = (b.
x() - a.
x()) * (shiftA.
y() - a.
y()) - (b.
y() - a.
y()) * (shiftA.
x() - a.
x()) >= 0;
368 buildingSide << a << shiftA << shiftB << b;
369 painter->drawPolygon(buildingSide);
373 outerRoof->
append(shiftA);
375 m_cachedOuterRoofPolygons.append(outerRoof);
377 for (
const QPolygonF *outline : std::as_const(m_cachedInnerPolygons)) {
378 if (outline->isEmpty()) {
382 int const size = outline->size();
384 innerRoof->
reserve(outline->size());
386 QPointF shiftA = a + buildingOffset(a, viewport);
387 innerRoof->append(shiftA);
388 for (
int i = 1; i < size; ++i) {
389 QPointF const &b = (*outline)[i];
390 QPointF const shiftB = b + buildingOffset(b, viewport);
392 bool backface = (b.
x() - a.
x()) * (shiftA.
y() - a.
y()) - (b.
y() - a.
y()) * (shiftA.
x() - a.
x()) >= 0;
396 buildingSide << a << shiftA << shiftB << b;
397 painter->drawPolygon(buildingSide);
401 innerRoof->append(shiftA);
403 m_cachedInnerRoofPolygons.append(innerRoof);
407 QList<QPolygonF *> fillPolygons = painter->createFillPolygons(m_cachedOuterPolygons, m_cachedInnerPolygons);
409 for (
QPolygonF *fillPolygon : std::as_const(fillPolygons)) {
410 painter->drawPolygon(*fillPolygon);
412 qDeleteAll(fillPolygons);
416void BuildingGraphicsItem::screenPolygons(
const ViewportParams &viewport,
417 const GeoDataPolygon *polygon,
423 viewport.screenCoordinates(polygon->outerBoundary(), outerPolygons);
426 for (
const GeoDataLinearRing &innerBoundary : innerBoundaries) {
428 viewport.screenCoordinates(innerBoundary, innerPolygonsPerBoundary);
430 innerPolygons.
reserve(innerPolygons.
size() + innerPolygonsPerBoundary.
size());
431 for (
QPolygonF *innerPolygonPerBoundary : std::as_const(innerPolygonsPerBoundary)) {
432 innerPolygons << innerPolygonPerBoundary;
437bool BuildingGraphicsItem::contains(
const QPoint &screenPosition,
const ViewportParams *viewport)
const
439 if (m_cachedOuterPolygons.isEmpty()) {
441 return AbstractGeoPolygonGraphicsItem::contains(screenPosition, viewport);
444 QPointF const point = screenPosition;
445 for (
auto polygon : m_cachedOuterRoofPolygons) {
447 for (
auto polygon : m_cachedInnerRoofPolygons) {
455 for (
auto polygon : m_cachedOuterPolygons) {
457 for (
auto polygon : m_cachedInnerPolygons) {
468bool BuildingGraphicsItem::configurePainterForFrame(GeoPainter *painter)
const
470 QPen currentPen = painter->pen();
472 GeoDataStyle::ConstPtr style = this->style();
474 painter->setPen(
QPen());
476 const GeoDataPolyStyle &polyStyle = style->polyStyle();
482 if (!polyStyle.fill()) {
485 const QColor paintedColor = polyStyle.paintedColor().
darker(150);
486 if (painter->brush().color() != paintedColor) {
487 painter->setBrush(paintedColor);
This file contains the headers for ViewportParams.
QString name(GameStandardAction id)
bool isValid(QStringView ifopt)
Binds a QML item to a specific geodetic location in screen coordinates.
@ HighQuality
High quality (e.g. antialiasing for lines)
QColor darker(int factor) const const
int pointSize() const const
void setPointSize(int pointSize)
QList< QScreen * > screens()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
Qt::PenStyle style() const const
bool isNull() const const
QPoint toPoint() const const
QRectF boundingRect() const const
bool containsPoint(const QPointF &point, Qt::FillRule fillRule) const const
QSizeF size() const const
qreal height() const const
qreal width() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QTextStream & center(QTextStream &stream)