QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FenceWallGeometry.cc
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10#include "FenceWallGeometry.h"
11
12#include <QtCore/QPointF>
13#include <QtCore/qminmax.h>
14#include <QtCore/qnumeric.h>
15
16#include <cmath>
17
18#include "GeoScene.h"
19#include "SurfacePatchModel.h"
20#include "TileMath.h"
21
22namespace {
23constexpr double kMaxSegmentMeters = 50.0;
24constexpr int kMaxSubdivisionsPerEdge = 64;
25constexpr int kFloatsPerVertex = 3;
26} // namespace
27
28FenceWallGeometry::FenceWallGeometry(QQuick3DObject* parent) : QQuick3DGeometry(parent)
29{
30 _rebuild();
31}
32
34{
35 if (scene == _scene) {
36 return;
37 }
38 if (_scene) {
39 disconnect(_scene, nullptr, this, nullptr);
40 }
41 _scene = scene;
42 if (_scene) {
43 // verticalScale follows the origin latitude, so z offsets drift on re-anchor
44 connect(_scene, &GeoScene::sceneOriginChanged, this, &FenceWallGeometry::_scheduleRebuild);
45 connect(_scene, &QObject::destroyed, this, [this] {
46 _scene = nullptr;
47 emit sceneChanged();
48 _scheduleRebuild();
49 });
50 }
51 emit sceneChanged();
52 _scheduleRebuild();
53}
54
56{
57 if (surfaceModel == _surfaceModel) {
58 return;
59 }
60 if (_surfaceModel) {
61 disconnect(_surfaceModel, nullptr, this, nullptr);
62 }
63 _surfaceModel = surfaceModel;
64 if (_surfaceModel) {
65 connect(_surfaceModel, &SurfacePatchModel::terrainHeightsChanged, this, &FenceWallGeometry::_scheduleRebuild);
66 connect(_surfaceModel, &QObject::destroyed, this, [this] {
67 _surfaceModel = nullptr;
69 _scheduleRebuild();
70 });
71 }
73 _scheduleRebuild();
74}
75
76void FenceWallGeometry::setCoordinates(const QVariantList& coordinates)
77{
78 if (coordinates == _coordinates) {
79 return;
80 }
81 _coordinates = coordinates;
82 _ring.clear();
83 _ring.reserve(coordinates.size());
84 for (const QVariant& variant : coordinates) {
85 const QGeoCoordinate coordinate = variant.value<QGeoCoordinate>();
86 if (coordinate.isValid()) {
87 _ring.append(coordinate);
88 }
89 }
90 // Tolerate an already-closed ring (circleCoordinates closes back to the start)
91 if ((_ring.size() > 1) && (_ring.first().distanceTo(_ring.last()) < 0.01)) {
92 _ring.removeLast();
93 }
94 emit coordinatesChanged();
95 _scheduleRebuild();
96}
97
98void FenceWallGeometry::setHeightAglMeters(qreal heightAglMeters)
99{
100 if (qFuzzyCompare(heightAglMeters, _heightAglMeters)) {
101 return;
102 }
103 _heightAglMeters = heightAglMeters;
105 _scheduleRebuild();
106}
107
108void FenceWallGeometry::_scheduleRebuild()
109{
110 if (_rebuildPending) {
111 return;
112 }
113 _rebuildPending = true;
114 QMetaObject::invokeMethod(this, &FenceWallGeometry::_rebuild, Qt::QueuedConnection);
115}
116
117void FenceWallGeometry::_rebuild()
118{
119 _rebuildPending = false;
120 if (!_scene || (_ring.size() < 3) || (_heightAglMeters <= 0.0)) {
121 _setAnchorCoordinate(QGeoCoordinate());
122 clear();
123 setStride(kFloatsPerVertex * sizeof(float));
124 setVertexData(QByteArray());
125 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
126 setBounds(QVector3D(), QVector3D());
127 update();
128 return;
129 }
130
131 // Subdivide each edge so the bottom edge drapes over terrain
132 QList<QGeoCoordinate> points;
133 for (qsizetype i = 0; i < _ring.size(); i++) {
134 const QGeoCoordinate& from = _ring[i];
135 const QGeoCoordinate& to = _ring[(i + 1) % _ring.size()];
136 const double distance = from.distanceTo(to);
137 const int steps = qBound(1, static_cast<int>(std::ceil(distance / kMaxSegmentMeters)), kMaxSubdivisionsPerEdge);
138 const double azimuth = from.azimuthTo(to);
139 points.append(from);
140 for (int step = 1; step < steps; step++) {
141 points.append(from.atDistanceAndAzimuth(distance * step / steps, azimuth));
142 }
143 }
144 points.append(points.first()); // close the ring
145
146 const QGeoCoordinate& anchor = points.first();
147 const double anchorTerrain = _terrainHeight(anchor);
148 _setAnchorCoordinate(QGeoCoordinate(anchor.latitude(), anchor.longitude(), anchorTerrain));
149
150 const QPointF anchorWorld = TileMath::geoToWorld(anchor);
151 const double verticalScale = _scene->verticalScale();
152 const float wallHeight = static_cast<float>(_heightAglMeters * verticalScale);
153
154 QByteArray vertexData;
155 vertexData.resize(points.size() * 2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float)));
156 float* vertex = reinterpret_cast<float*>(vertexData.data());
157 QVector3D minBounds;
158 QVector3D maxBounds;
159 for (qsizetype i = 0; i < points.size(); i++) {
160 const QPointF world = TileMath::geoToWorld(points[i]);
161 const float x = static_cast<float>(world.x() - anchorWorld.x());
162 const float y = static_cast<float>(world.y() - anchorWorld.y());
163 const float zBottom = static_cast<float>((_terrainHeight(points[i]) - anchorTerrain) * verticalScale);
164 *vertex++ = x;
165 *vertex++ = y;
166 *vertex++ = zBottom;
167 *vertex++ = x;
168 *vertex++ = y;
169 *vertex++ = zBottom + wallHeight;
170 const QVector3D bottom(x, y, zBottom);
171 const QVector3D top(x, y, zBottom + wallHeight);
172 if (i == 0) {
173 minBounds = bottom;
174 maxBounds = top;
175 } else {
176 minBounds = QVector3D(qMin(minBounds.x(), x), qMin(minBounds.y(), y), qMin(minBounds.z(), zBottom));
177 maxBounds = QVector3D(qMax(maxBounds.x(), x), qMax(maxBounds.y(), y), qMax(maxBounds.z(), top.z()));
178 }
179 }
180
181 clear();
182 setStride(kFloatsPerVertex * sizeof(float));
183 setVertexData(vertexData);
184 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
185 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
186 setBounds(minBounds, maxBounds);
187 update();
188}
189
190double FenceWallGeometry::_terrainHeight(const QGeoCoordinate& coordinate) const
191{
192 if (!_surfaceModel) {
193 return 0.0;
194 }
195 const double height = _surfaceModel->terrainHeightAt(coordinate);
196 return std::isfinite(height) ? height : 0.0;
197}
198
199void FenceWallGeometry::_setAnchorCoordinate(const QGeoCoordinate& anchorCoordinate)
200{
201 if (anchorCoordinate == _anchorCoordinate) {
202 return;
203 }
204 _anchorCoordinate = anchorCoordinate;
206}
int32_t height
Definition UBXPlan.cc:44
void anchorCoordinateChanged()
void setScene(GeoScene *scene)
QVariantList coordinates() const
void surfaceModelChanged()
SurfacePatchModel * surfaceModel() const
void coordinatesChanged()
GeoScene * scene() const
FenceWallGeometry(QQuick3DObject *parent=nullptr)
void setSurfaceModel(SurfacePatchModel *surfaceModel)
void setCoordinates(const QVariantList &coordinates)
void heightAglMetersChanged()
void setHeightAglMeters(qreal heightAglMeters)
QGeoCoordinate anchorCoordinate() const
qreal heightAglMeters() const
qreal verticalScale() const
Definition GeoScene.cc:48
void sceneOriginChanged()
void terrainHeightsChanged()
terrainHeightAt answers changed somewhere: consumers re-query
Q_INVOKABLE double terrainHeightAt(const QGeoCoordinate &coordinate) const
double distance(const Ecef &left, const Ecef &right)
Straight-line distance between left and right, in metres.
QPointF geoToWorld(const QGeoCoordinate &coord)
Geo -> world meters. Latitude is clamped to +/-kMaxLatitude.
Definition TileMath.cc:23