Cogs.Core
ClipmapQueryHandler.cpp
1#include "ClipmapQueryHandler.h"
2#include "ClipmapLevel.h"
3#include "RenderContext.h"
4#include "Raster/RasterSource.h"
5
6#include "Foundation/Logging/Logger.h"
7
8#include <algorithm>
9
10namespace
11{
12 Cogs::Logging::Log logger = Cogs::Logging::getLogger("ClipmapQueryHandler");
13}
14
15void Cogs::ClipmapQueryHandler::initialize(std::vector<ClipmapLevel> * terrainLevels, std::vector<std::vector<ClipmapLevel>> * imagery)
16{
17 this->imagery = imagery;
18 this->terrainLevels = terrainLevels;
19}
20
21void Cogs::ClipmapQueryHandler::postInitialize()
22{
23 std::lock_guard<std::mutex> lock(mutex);
24
25 for (auto & data : elevationQueries) {
26 if (!data.inputLevels) {
27 data.inputLevels = terrainLevels;
28
29 if (data.targetLevel == -1) {
30 auto terrainSource = (*terrainLevels)[0].rasterLevel->getSource();
31 data.targetLevel = static_cast<int>(terrainSource->getLevels().size()) - 1;
32 }
33 }
34 }
35
36 for (auto & data : colorQueries) {
37 if (!data.inputLevels) {
38 data.inputLevels = &(*imagery)[data.queryLayer - 1];
39
40 if (data.targetLevel == -1) {
41 data.targetLevel = static_cast<int>((*imagery)[data.queryLayer - 1].size()) - 1;
42 }
43 }
44 }
45
46 allowToProcessQueries = true;
47}
48
49void Cogs::ClipmapQueryHandler::postRequest(Cogs::TerrainQuery * query, Cogs::TerrainQueryCallback callback, const WorldOptions & worldOptions)
50{
51 if (query->layer == 0) {
52 auto terrainSource = (terrainLevels && !terrainLevels->empty()) ? (*terrainLevels)[0].rasterLevel->getSource() : nullptr;
53
54 ElevationQueryData data;
55 data.userData = query->userData;
56 data.callback = callback;
57 data.positions.assign(query->positions, query->positions + query->numPositions);
58 data.results.resize(query->numPositions / 2, 0.0);
59 data.resultStride = 1;
60 data.levels.resize(query->numPositions / 2, (int)-1);
61 data.inputLevels = terrainLevels;
62 data.queryLayer = query->layer;
63 data.worldOptions = worldOptions;
64
65 data.targetLevel = query->level;
66
67 if (data.targetLevel == -1 && terrainSource) {
68 data.targetLevel = static_cast<int>(terrainSource->getLevels().size()) - 1;
69 }
70
71 data.requestedLevel.resize(query->numPositions / 2, -1);
72 data.resultLevel = -1;
73 data.complete = false;
74
75 std::lock_guard<std::mutex> lock(mutex);
76 elevationQueries.push_back(data);
77 } else {
78 assert((!imagery || ((query->layer - 1) >= 0 && (query->layer - 1) < (int)(*imagery).size())) && "Query layer not a valid imagery layer.");
79
80 ColorQueryData data;
81 data.userData = query->userData;
82 data.callback = callback;
83 data.positions.assign(query->positions, query->positions + query->numPositions);
84 data.results.resize(4 * (query->numPositions / 2), 0);
85 data.resultStride = 4;
86 data.levels.resize(query->numPositions / 2, (int)-1);
87 data.queryLayer = query->layer;
88 data.worldOptions = worldOptions;
89
90 if (imagery) {
91 data.inputLevels = &(*imagery)[query->layer - 1];
92 data.targetLevel = query->level == -1 ? static_cast<int>((*imagery)[query->layer - 1].size()) - 1 : query->level;
93 } else {
94 data.inputLevels = nullptr;
95 data.targetLevel = query->level;
96 }
97
98 data.requestedLevel.resize(query->numPositions / 2, -1);
99 data.resultLevel = -1;
100 data.complete = false;
101
102 std::lock_guard<std::mutex> lock(mutex);
103 colorQueries.push_back(data);
104 }
105}
106
107void Cogs::ClipmapQueryHandler::processQueries()
108{
109 if (!allowToProcessQueries) return;
110
111 processQueries_T(elevationQueries);
112 processQueries_T(colorQueries);
113}
114
115template<>
116bool Cogs::ClipmapQueryHandler::readData(const RasterTileData * tileData, const int x, const int y, const int width, double * result)
117{
118 const size_t requiredSize = (y * width + x + 1) * sizeof(float);
119
120 if (tileData->data.size() < requiredSize) {
121 LOG_ERROR(logger, "Tile data insufficient size to query (x, y, width): %d, %d, %d", x, y, width);
122 return false;
123 }
124
125 const float * elevations = reinterpret_cast<const float *>(tileData->data.data());
126
127 *result = elevations[y * width + x];
128
129 return true;
130}
131
132template<>
133bool Cogs::ClipmapQueryHandler::readData(const RasterTileData * tileData, const int x, const int y, const int width, unsigned char * result)
134{
135 const size_t requiredSize = (y * width + x + 1) * sizeof(float);
136
137 if (tileData->data.size() < requiredSize) {
138 LOG_ERROR(logger, "Tile data insufficient size to query (x, y, width): %d, %d, %d", x, y, width);
139 return false;
140 }
141
142 const auto & colors = tileData->data;
143
144 int bpp = 0;
145 int stride = 0;
146
147 switch (tileData->format) {
148 case TextureFormat::R8G8B8_UNORM:
149 bpp = 3;
150 break;
151 case TextureFormat::R8G8B8A8_UNORM:
152 bpp = 4;
153 break;
154 case TextureFormat::R8G8B8A8_UNORM_SRGB:
155 bpp = 4;
156 break;
157 case TextureFormat::R32_FLOAT:
158 bpp = 4;
159 break;
160 default:
161 // Format not supported for query.
162 break;
163 }
164
165 if (!bpp) return false;
166
167 // Assign full alpha to result.
168 result[3] = 255;
169
170 stride = bpp * width;
171
172 // We don't want to read outside data array.
173 if (static_cast<int>(colors.size()) < (y * stride + (x + 1) * bpp)) return false;
174
175 for (int i = 0; i < bpp; ++i) {
176 result[i] = colors[y * stride + x * bpp + i];
177 }
178
179 return true;
180}
181
182template<typename DataType>
183void Cogs::ClipmapQueryHandler::processQueries_T(std::vector<TerrainQueryData<DataType>> & queries)
184{
185 std::vector<size_t> completed;
186 std::vector<TerrainQueryData<DataType>> currentQueries;
187
188 {
189 std::lock_guard<std::mutex> lock(mutex);
190
191 currentQueries = std::move(queries);
192 }
193
194 for (size_t i = 0; i < currentQueries.size(); ++i) {
195 TerrainQueryData<DataType> & data = currentQueries[i];
196
197 std::vector<ClipmapLevel> & levels = *data.inputLevels;
198
199 for (size_t j = 0; j < data.levels.size(); ++j) {
200 if (data.levels[j] != data.targetLevel && data.levels[j] <= data.resultLevel && !levels.empty()) {
201 const int level = std::min(data.targetLevel, data.resultLevel <= 0 ? data.resultLevel + 1 : data.resultLevel * 2);
202
203 assert(level >= 0 && level < static_cast<int>(levels.size()));
204 const auto rasterLevel = levels[level].rasterLevel;
205
206 // Positions are stored as [long, lat] pairs (x, y).
207 const double queryPositionLong = data.positions[j * 2];
208 const double queryPositionLat = data.positions[j * 2 + 1];
209
210 auto & extent = rasterLevel->getGeoExtent();
211 if (queryPositionLong < extent.getWest() ||
212 queryPositionLong > extent.getEast() ||
213 queryPositionLat < extent.getSouth() ||
214 queryPositionLat > extent.getNorth()) {
215 LOG_WARNING(logger, "Query outside extents: [%f, %f].", queryPositionLong, queryPositionLat);
216
217 // Do not process this index any more.
218 data.levels[j] = level;
219
220 continue;
221 }
222
223 const double latitudeIndex = rasterLevel->latitudeToIndex(queryPositionLat / data.worldOptions.worldScale.y);
224 const double longitudeIndex = rasterLevel->longitudeToIndex(queryPositionLong / data.worldOptions.worldScale.x);
225
226 const int tileY = static_cast<int>(latitudeIndex / rasterLevel->getLatitudePostsPerTile());
227 const int tileX = static_cast<int>(longitudeIndex / rasterLevel->getLongitudePostsPerTile());
228
229 if (tileX < 0 || tileY < 0) {
230 LOG_WARNING(logger, "Query outside valid range: [%f, %f].", queryPositionLong, queryPositionLat);
231
232 // Do not process this index any more.
233 data.levels[j] = level;
234
235 continue;
236 }
237
238 RasterTileIdentifier id = { rasterLevel->getLevel(), tileX, tileY };
239
240 auto source = rasterLevel->getSource();
241
242 {
243 std::lock_guard<RasterSource> sourceLock(*source);
244
245 const auto tile = source->getTile(id);
246
247 if (!tile) {
248 LOG_WARNING(logger, "Error reading tile %d, %d, %d from source.", id.level, id.x, id.y);
249
250 data.levels[j] = level;
251
252 continue;
253 }
254
255 const auto tileData = source->getTileData(tile);
256
257 if (tileData) {
258 const int stride = tile->getWidth();
259 const int y = static_cast<int>(latitudeIndex) % rasterLevel->getLatitudePostsPerTile();
260 const int x = static_cast<int>(longitudeIndex) % rasterLevel->getLongitudePostsPerTile();
261
262 if (tileData->data.size() && readData(tileData, x, y, stride, &data.results[j * data.resultStride])) {
263 data.levels[j] = level;
264
265 if (data.requestedLevel[j] == level) {
266 // We have read from the current detail level. No longer need to pin the tile data.
267 source->unrefTile(tile);
268 }
269 } else {
270 if (tileData->data.empty()) {
271 LOG_WARNING(logger, "Empty tile data for tile at %d, %d, %d.", id.level, id.x, id.y);
272 } else {
273 LOG_WARNING(logger, "Failed reading data from tile at %d, %d, %d.", id.level, id.x, id.y);
274 }
275
276 data.levels[j] = level;
277 }
278 } else {
279 if (level > data.requestedLevel[j]) {
280 TileLoadRequest request = { tile };
281 data.requestedLevel[j] = level;
282 source->refTile(tile);
283 source->requestTile(request);
284 }
285 }
286 }
287 }
288 }
289
290 bool done = true;
291 int doneLevel = std::numeric_limits<int>::max();
292
293 for (size_t j = 0; j < data.levels.size(); ++j) {
294 done &= (data.levels[j] >= 0);
295 doneLevel = std::min(doneLevel, data.levels[j]);
296 }
297
298 if (done && doneLevel > data.resultLevel) {
299 TerrainQueryResults results;
300 results.userData = data.userData;
301 results.numResults = static_cast<int>(data.results.size());
302 results.results = data.results.data();
303 results.resultLevel = doneLevel;
304
305 data.callback(&results);
306
307 data.resultLevel = doneLevel;
308 }
309
310 data.complete = doneLevel == data.targetLevel;
311 }
312
313 std::lock_guard<std::mutex> lock(mutex);
314
315 // Insert queries that are not completed back in the queue and retry on next iteration.
316 for (auto & query : currentQueries) {
317 if (!query.complete) {
318 queries.emplace_back(std::move(query));
319 }
320 }
321}
Log implementation class.
Definition: LogManager.h:139
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180