Cogs.Core
OGC3DTilesSubtree.cpp
1#include "OGC3DTilesSubtree.h"
2
3#include "Systems/OGC3DTilesSystem.h"
4#include "ExtensionRegistry.h"
5
6#include <Context.h>
7#include <Engine.h>
8
9#include <Foundation/Logging/Logger.h>
10#include <Foundation/Platform/IO.h>
11#include <Foundation/BitTwiddling/MortonCode.h>
12
13#include <Serialization/JsonParser.h>
14
15#include <tuple>
16#include <cassert>
17#include <bitset>
18
19using namespace Cogs::Core;
20using namespace Cogs::Core::OGC3DTilesSubtree;
21using namespace std::chrono_literals;
22
27namespace {
28 Cogs::Logging::Log logger = Cogs::Logging::getLogger("OGC3DTilesSubtree");
29
30 // Ref: https://github.com/CesiumGS/3d-tiles/tree/draft-1.1/specification/ImplicitTiling#subtree-binary-format
31 struct Header {
32 uint32_t magic;
33 uint32_t version;
34 uint64_t jsonLength;
35 uint64_t bitstreamLength;
36 };
37
38 // Will interleave A and B, bitwise.
39 inline uint32_t interleaveBits(uint16_t a, uint16_t b)
40 {
41 return Cogs::mortonCode(a, b);
42 }
43
44 // Will interleave A, B and C, bitwise.
45 inline uint64_t interleaveBits(uint16_t a, uint16_t b, uint16_t c)
46 {
47 // FIXME: We should do some checking here to see if the result will exceed 32bit
48 // and maybe print some warnings. If not, prepare the code for 64-bits morton codes
49 // in OctTree-mode.
50 // NOTE: I assume that a Morton code which exceeds 32bit is not realistic in our scenario,
51 // expecially when it comes to checking the content/subtree availability bitset. A 32bit+ index
52 // would mean that the subtree-bitset would exceed 0xffffff+ bytes in length, ie. 16+ MB.
53 return Cogs::mortonCode(a, b, c);
54 }
55
56 inline uint32_t concatBits(uint16_t a, uint16_t b, int numBitsFromB)
57 {
58 b = b << (16 - numBitsFromB);
59 uint32_t ax = uint32_t(a) << 16;
60 ax = ax + b;
61 return ax >> (16 - numBitsFromB);
62 }
63}
64
65#if 0
66// Debug utility
67// NOTE: The Subtree-object needs to have been given a correct 'levelsPerSubtree' value from the tileset
68void
69printAvailableSubtrees(const Subtree* subtree)
70{
71 int bitstreamIdx = subtree->childSubtreeAvailability.bitstreamIdx;
72 int level = subtree->globalCoord.level + subtree->levelsPerSubtree;
73 int range = (int)std::pow(2, level);
74 bool isOctTree = subtree->globalCoord.z >= 0;
75
76 printf("subtree->globalCoord.level=%d, levelsPerSubtree=%d\n", subtree->globalCoord.level, subtree->levelsPerSubtree);
77 LOG_DEBUG(logger, "printAvailableSubtrees(): level=%d, range=%d, section=%d (x=%d, y=%d, z=%d):",
78 level, range, bitstreamIdx, subtree->globalCoord.x, subtree->globalCoord.y, subtree->globalCoord.z);
79
80 if (subtree->childSubtreeAvailability.availableCount == 0) {
81 LOG_DEBUG(logger, " => No subtrees available\n");
82 return;
83 }
84
85 for (int x = 0; x < range; ++x) {
86 for (int y = 0; y < range; ++y) {
87 for (int z = 0; z < range; ++z) {
88 uint32_t mortonIndex = 0;
89 if (isOctTree) {
90 mortonIndex = interleaveBits(x, y, z);
91 }
92 else {
93 mortonIndex = interleaveBits(x, y);
94 }
95
96 uint32_t levelOffset = 0; // The subtree-array is always a single array/level.
97 uint32_t offset = levelOffset + mortonIndex;
98
99 assert(bitstreamIdx < subtree->bitstreamSections.size() && "Index out of bounds");
100 const BitstreamSection& section = subtree->bitstreamSections[bitstreamIdx];
101
102 auto bsBegin = subtree->bitstreams[section.bitstreamIdx].cbegin();
103 std::vector<unsigned char>::const_iterator start = bsBegin + section.startIdx;
104 std::vector<unsigned char>::const_iterator end = bsBegin + section.startIdx + section.length;
105 std::vector<unsigned char> buf(start, end);
106
107 int courseIdx = offset >> 3; // Divide by 8
108 int fineIdx = offset & 0b111;
109
110 assert(level >= 0 && "Internal error");
111 assert(courseIdx < buf.size() && "Index out of bounds");
112 unsigned char val = buf[courseIdx];
113 std::bitset<8> valBits(val);
114
115 bool isSet = valBits.test(fineIdx);
116 if (isSet) {
117 if (isOctTree) LOG_DEBUG(logger, " %d.%d.%d.%d.subtree\n", level, x, y, z);
118 else LOG_DEBUG(logger, " %d.%d.%d.subtree\n", level, x, y);
119
120 LOG_DEBUG(logger, " parent?: %d:%d|%d\n", level - 1, x >> 1, y >> 1);
121 }
122
123 if (!isOctTree) {
124 break;
125 }
126 }
127 }
128 }
129}
130#endif
131
132bool
133parseAvailabilityJSON(const Value& value, Availability* target)
134{
135 target->allIsAvailable = false; // We'll assume all tiles are available unless specified
136 target->availableCount = 0;
137
138 if (value.HasMember("constant")) {
139 target->allIsAvailable = value["constant"].GetInt() >= 1;
140 }
141
142 if (!target->allIsAvailable && value.MemberCount() > 1) {
143 if (value.HasMember("availableCount")) {
144 target->availableCount = value["availableCount"].GetInt();
145 }
146 else {
147 LOG_ERROR(logger, "Availability struct must contain a 'availableCount' member if 'constant=0' or undefined.");
148 return false;
149 }
150
151 if (value.HasMember("bitstream")) {
152 target->bitstreamIdx = value["bitstream"].GetInt();
153 }
154 else {
155 if (target->availableCount != 0) {
156 LOG_ERROR(logger, "Availability struct must contain a 'bitstream' member if 'constant==0' or undefined and 'availibilityCount != 0'.");
157 return false;
158 }
159 }
160 }
161
162 return true;
163}
164
170void
171fetchSubtreeBufferFile(Context* context, const std::string& URI, const std::string& originURL, Subtree* target, int bufferIdx, uint32_t uniqueComponentId)
172{
173 assert(bufferIdx < int(target->bitstreamSections.size()));
174
175 size_t pos = originURL.rfind("/");
176 assert(pos != std::string::npos && "Invalid originURL");
177 std::string baseURL = originURL.substr(0, pos);
178 std::string newURL = baseURL + "/" + URI;
179
180 // Does the originURL contain an "access_token" parameter?
181 // FIXME: Do we need to do the same with the "key=" session keys used by some servers?
182 size_t accessTokenPos = originURL.find("access_token");
183 if (accessTokenPos != std::string::npos) {
184 size_t endPos = originURL.find("&", accessTokenPos);
185 if (endPos == std::string::npos) {
186 endPos = originURL.size();
187 }
188 std::string accessToken = originURL.substr(accessTokenPos, endPos - accessTokenPos);
189 newURL += "?" + accessToken;
190 }
191
192 target->bitstreams.resize(target->bitstreamSections.size());
193
194 // FIXME: We should explicitly register WHICH subtrees we are waiting for. This will make it possible
195 // to start processing before all subtrees are loaded (see OGC3DTilesSubtree::getChildren() f.ex).
196 target->pendingRequests += 1;
197
198 (void)DataFetcherManager::fetchAsync(context, newURL,
199 [context, target, bufferIdx, uniqueComponentId](std::unique_ptr<Cogs::FileContents> inData) {
200 if (OGC3DTilesSystem::componentIsStale(context, uniqueComponentId) || !inData.get()) {
201 return;
202 }
203
204 auto data = inData.release();
205 BitstreamSection section = target->bitstreamSections[bufferIdx];
206 target->bitstreams[section.bitstreamIdx].reserve(section.length);
207 for (size_t j = 0; j < section.length; ++j) {
208 target->bitstreams[section.bitstreamIdx].emplace_back(data->ptr[section.startIdx + j]);
209 }
210 target->pendingRequests -= 1;
211
212 // As this request is an async prosess independent of the original subtree-request,
213 // we'll have to flag that the 3DTiles component needs an update.
214 OGC3DTilesSystem* system = ExtensionRegistry::getExtensionSystem<OGC3DTilesSystem>(context);
215 for (auto& comp : system->pool) {
216 OGC3DTilesDataHolder& h = system->getData(&comp);
217 OGC3DTilesData* componentState = h.data.get();
218 if (componentState->uniqueComponentId == uniqueComponentId) {
219 context->engine->setDirty();
220 break;
221 }
222 }
223
224 delete data;
225 });
226}
227
228bool
229parseSubtreeBufferJSON(Context* context, std::string_view json, Subtree* target, const std::string& originURL, uint32_t uniqueComponentId)
230{
231 Document doc = parseJson(json, JsonParseFlags::None);
232
233 std::vector<std::vector<std::pair<uint32_t, uint32_t>>> bufferSections;
234 if (doc.HasMember("bufferViews")) {
235 GenericArray array = doc["bufferViews"].GetArray();
236 for (Value& bv : array) {
237 if (!(bv.HasMember("buffer") && bv.HasMember("byteOffset") && bv.HasMember("byteLength"))) {
238 LOG_ERROR(logger, "No 'buffer', 'byteOffset' and 'byteLength' in 'bufferViews' section");
239 return false;
240 }
241 BitstreamSection bss{ (uint8_t)bv["buffer"].GetUint(), bv["byteOffset"].GetUint(), bv["byteLength"].GetUint() };
242 target->bitstreamSections.emplace_back(bss);
243 }
244 }
245
246 bool ok = true;
247 if (doc.HasMember("tileAvailability")) {
248 ok &= parseAvailabilityJSON(doc["tileAvailability"], &target->tileAvailability);
249 if (!ok) {
250 LOG_ERROR(logger, "Could not parse 'tileAvailability'");
251 }
252
253 if (!target->tileAvailability.allIsAvailable && target->tileAvailability.availableCount != 0) {
254 assert(target->tileAvailability.bitstreamIdx < target->bitstreams.size() && "Bitstream index out of bounds");
255 }
256 }
257 else {
258 LOG_ERROR(logger, "Subtree JSON did not contain a 'tileAvailability' section.");
259 return false;
260 }
261
262 if (doc.HasMember("contentAvailability")) {
263 GenericArray array = doc["contentAvailability"].GetArray();
264 target->contentAvailability.resize(array.Size());
265 int i = 0;
266 for (Value& c : array) {
267 ok &= parseAvailabilityJSON(c, &target->contentAvailability[i]);
268 if (!ok) {
269 LOG_ERROR(logger, "Could not parse 'contentAvailability' nr %d", (i + 1));
270 }
271 i += 1;
272 }
273 }
274 else {
275 LOG_ERROR(logger, "Subtree JSON did not contain a 'contentAvailability' section.");
276 return false;
277 }
278
279 if (doc.HasMember("childSubtreeAvailability")) {
280 ok &= parseAvailabilityJSON(doc["childSubtreeAvailability"], &target->childSubtreeAvailability);
281 if (!ok) {
282 LOG_ERROR(logger, "Could not parse 'childSubtreeAvailability'");
283 }
284 }
285 else {
286 LOG_ERROR(logger, "Subtree JSON did not contain a 'childSubtreeAvailability' section.");
287 return false;
288 }
289
290 if (doc.HasMember("buffers") && target->tileAvailability.availableCount > 0) {
291 GenericArray array = doc["buffers"].GetArray();
292 int bufferIdx = 0;
293 for (Value& buf : array) {
294 assert(buf.HasMember("byteLength"));
295
296 if (buf.HasMember("uri")) {
297 //LOG_DEBUG(logger, "Subtree.buffer is not of type 'Internal Buffer'. Fetching '%s'", buf["uri"].GetString());
298 fetchSubtreeBufferFile(context, buf["uri"].GetString(), originURL, target, bufferIdx, uniqueComponentId);
299 }
300 else {
301 // Default/internal buffer which is already loaded
302 assert(bufferIdx == 0 && "Internal buffer must always be index 0");
303 }
304 ++bufferIdx;
305 }
306 }
307 else {
308 if (!target->tileAvailability.allIsAvailable && target->tileAvailability.availableCount > 0) {
309 LOG_ERROR(logger, "Subtree JSON has sparse tile-availibility but did not contain a 'buffers' section.");
310 return false;
311 }
312 }
313
314 return ok;
315}
316
317bool
318parseSubtreeBufferBinary(Context* context, const Cogs::Memory::MemoryBuffer* buf, Subtree* target, const std::string& originURL, uint32_t uniqueComponentId)
319{
320 const uint8_t* data = static_cast<const uint8_t*>(buf->data());
321 const Header* h = (const Header*)data;
322
323 if (h->magic != 0x74627573) {
324 LOG_ERROR(logger, "Subtree header magic value was not 0x74627573 (was 0x%x)", h->magic);
325 return false;
326 }
327
328 if (h->version != 1) {
329 LOG_ERROR(logger, "Subtree version was not '1' (was %d)", h->version);
330 return false;
331 }
332
333 // Read bitstreams before we parse the JSON for easier validation.
334 if (h->bitstreamLength > 0) {
335 // As a default we'll always load the data into bitstream #1.
336 uint32_t paddedJSONLength = static_cast<uint32_t>(h->jsonLength);
337 uint32_t offset = sizeof(Header) + paddedJSONLength;
338 const uint8_t* bitstreamStartPtr = &(static_cast<const uint8_t*>(buf->data())[offset]);
339 target->bitstreams.emplace_back(std::vector<uint8_t>(bitstreamStartPtr, bitstreamStartPtr + h->bitstreamLength));
340 }
341 else {
342 LOG_ERROR(logger, "parseSubtreeBufferBinary(): BitstreamLength value in header <= 0");
343 return false;
344 }
345
346 const char* jsonStartPtr = &(static_cast<const char*>(buf->data())[sizeof(Header)]);
347 const std::string json = std::string(jsonStartPtr, h->jsonLength);
348 bool ok = parseSubtreeBufferJSON(context, json, target, originURL, uniqueComponentId);
349 if (!ok) {
350 return false;
351 }
352
353#if 0
354 if (target->bitstreamSections.size() > 1) {
355 LOG_DEBUG(logger, "parseSubtreeBufferBinary(): Bitstream sections:");
356 for (int x = 1; x < target->bitstreamSections.size(); ++x) {
357 BitstreamSection section = target->bitstreamSections[x];
358 int bufIdx = section.bitstreamIdx;
359 int start = section.startIdx;
360 int len = section.length;
361
362 LOG_DEBUG(logger, " #%d: BufferIdx=%d, start=%d, len=%d => [ ", x, bufIdx, start, len);
363 for (int i = (len - 1); i >= 0; --i) {
364 std::bitset<8> x(target->bitstreams[bufIdx][i]);
365 std::cout << x << ' ';
366 }
367 LOG_DEBUG(logger, "] (NB: read backwards)\n");
368 }
369 }
370#endif
371 return true;
372}
373
374std::vector<OGC3DTiles::Coord>
375OGC3DTilesSubtree::getLeafNodesSubtreeCoords(const Subtree* subtree, const OGC3DTiles::Coord& leafNodeCoord)
376{
377 std::vector<OGC3DTiles::Coord> coords;
378
379 if (subtree->childSubtreeAvailability.allIsAvailable) {
380 return getAllChildren(subtree, leafNodeCoord);
381 }
382
383 if (subtree->childSubtreeAvailability.availableCount == 0 || subtree->pendingRequests > 0) {
384 return coords;
385 }
386
387 const int bitstreamIdx = subtree->childSubtreeAvailability.bitstreamIdx;
388 const int level = subtree->globalCoord.level + subtree->levelsPerSubtree;
389 const bool isOctTree = subtree->globalCoord.z >= 0;
390
391 const BitstreamSection& section = subtree->bitstreamSections[bitstreamIdx];
392 const OGC3DTiles::Coord localCoord = globalToLocal(subtree, leafNodeCoord);
393
394 // Sanity check: If these assert fails, then something is seriously wrong...
395 assert((localCoord.x * 2 + 1) < 0xFFFF && "LocalCoord.x*2+1 offset out of the 16bit bounds");
396 assert((localCoord.y * 2 + 1) < 0xFFFF && "LocalCoord.y*2+1 offset out of the 16bit bounds");
397 assert((isOctTree ? (localCoord.z * 2 + 1) : 0) < 0xFFFF && "LocalCoord.z*2+1 offset out of the 16bit bounds");
398 assert(level >= 0 && "Internal error");
399
400 const uint16_t xOffset = static_cast<uint16_t>(localCoord.x * 2);
401 const uint16_t yOffset = static_cast<uint16_t>(localCoord.y * 2);
402 const uint16_t zOffset = static_cast<uint16_t>(isOctTree ? localCoord.z * 2 : 0);
403
404 coords.reserve(xOffset * yOffset * (isOctTree ? zOffset : 1));
405
406 const size_t bufSize = section.length;
407 const std::vector<uint8_t>& bitarray = subtree->bitstreams[section.bitstreamIdx];
408
409 for (uint16_t x = xOffset; x <= xOffset + 1; ++x) {
410 for (uint16_t y = yOffset; y <= yOffset + 1; ++y) {
411 for (uint16_t z = zOffset; z <= zOffset + 1; ++z) {
412 uint64_t mortonIndex = 0;
413 if (isOctTree) {
414 mortonIndex = interleaveBits(x, y, z);
415 }
416 else {
417 mortonIndex = interleaveBits(x, y);
418 }
419 assert(mortonIndex < 0xFFFFFFFF && "Internal error. Morton index is way too large...");
420
421 const uint32_t courseIdx = static_cast<uint32_t>(mortonIndex >> 3); // Divide by 8
422 const uint32_t fineIdx = mortonIndex & 0b111;
423
424 assert((section.startIdx + courseIdx) < int(bufSize) && "Index out of bounds");
425 const uint8_t val = bitarray[section.startIdx + courseIdx];
426 if (val) {
427 std::bitset<8> valBits(val);
428 if (valBits.test(fineIdx)) {
429 coords.emplace_back(OGC3DTiles::Coord{ level, x, y, isOctTree ? z : -1 });
430 }
431 }
432 if (!isOctTree) {
433 break;
434 }
435 }
436 }
437 }
438
439 return coords;
440}
441
442bool
443OGC3DTilesSubtree::isLeafNodeInSubtree(const Subtree* subtree, const OGC3DTiles::Coord& coord)
444{
445 assert(coord.level <= subtree->globalCoord.level + subtree->levelsPerSubtree && "Coord level is deeper into subtree than allowed max depth.");
446 const bool isLeaf = coord.level == subtree->globalCoord.level + (subtree->levelsPerSubtree - 1);
447 return isLeaf;
448}
449
454std::vector<OGC3DTiles::Coord>
455OGC3DTilesSubtree::getAllChildren(const Subtree* subtree, const OGC3DTiles::Coord& parent)
456{
457 const bool isOctTree = subtree->globalCoord.z >= 0;
458 const OGC3DTiles::Coord localCoord = globalToLocal(subtree, parent);
459 const int localLevel = localCoord.level + 1;
460
461 std::vector<OGC3DTiles::Coord> children;
462 children.reserve(isOctTree ? 8 : 4);
463
464 // Sanity check: If this assert fails, then something is seriously wrong...
465 assert((localCoord.x * 2 + 1) < 0xFFFF && "LocalCoord.x*2+1 offset out of the 16bit bounds");
466 assert((localCoord.y * 2 + 1) < 0xFFFF && "LocalCoord.y*2+1 offset out of the 16bit bounds");
467 assert((isOctTree ? (localCoord.z * 2 + 1) : 0) < 0xFFFF && "LocalCoord.z*2+1 offset out of the 16bit bounds");
468
469 const uint16_t xOffset = static_cast<uint16_t>(localCoord.x * 2);
470 const uint16_t yOffset = static_cast<uint16_t>(localCoord.y * 2);
471 const uint16_t zOffset = static_cast<uint16_t>(isOctTree ? localCoord.z * 2 : 0);
472
473 for (uint16_t x = xOffset; x <= xOffset + 1; ++x) {
474 for (uint16_t y = yOffset; y <= yOffset + 1; ++y) {
475 for (uint16_t z = zOffset; z <= zOffset + 1; ++z) {
476 int numbits = localLevel;
477 int32_t gx = concatBits(static_cast<uint16_t>(subtree->globalCoord.x), x, numbits);
478 int32_t gy = concatBits(static_cast<uint16_t>(subtree->globalCoord.y), y, numbits);
479 int32_t gz = isOctTree ? concatBits(static_cast<uint16_t>(subtree->globalCoord.z), z, numbits) : -1;
480 OGC3DTiles::Coord childCoord = OGC3DTiles::Coord{ subtree->globalCoord.level + localLevel, gx, gy, gz };
481 children.emplace_back(childCoord);
482
483 if (!isOctTree) {
484 break;
485 }
486 }
487 }
488 }
489
490 assert((isOctTree && children.size() == 8) || (!isOctTree && children.size() == 4) && "Internal error");
491
492 return children;
493}
494
499std::vector<OGC3DTiles::Coord>
500OGC3DTilesSubtree::getChildren(const Subtree* subtree, const OGC3DTiles::Coord& parent)
501{
502 std::vector<OGC3DTiles::Coord> children;
503 if (subtree->pendingRequests > 0) {
504 return children;
505 }
506
507 int bitstreamIdx = subtree->tileAvailability.bitstreamIdx;
508 assert(bitstreamIdx < int(subtree->bitstreamSections.size()) && "Index out of bounds");
509 const OGC3DTilesSubtree::BitstreamSection& section = subtree->bitstreamSections[bitstreamIdx];
510
511 const bool isOctTree = subtree->globalCoord.z >= 0;
512 const float N = isOctTree ? 8.0f : 4.0f;
513 const OGC3DTiles::Coord localCoord = globalToLocal(subtree, parent);
514 const int localLevel = localCoord.level + 1;
515
516 // Sanity check: If this assert fails, then something is seriously wrong...
517 assert((localCoord.x * 2 + 1) < 0xFFFF && "LocalCoord.x*2+1 offset out of the 16bit bounds");
518 assert((localCoord.y * 2 + 1) < 0xFFFF && "LocalCoord.y*2+1 offset out of the 16bit bounds");
519 assert((isOctTree ? (localCoord.z * 2 + 1) : 0) < 0xFFFF && "LocalCoord.z*2+1 offset out of the 16bit bounds");
520
521 const uint16_t xOffset = static_cast<uint16_t>(localCoord.x * 2);
522 const uint16_t yOffset = static_cast<uint16_t>(localCoord.y * 2);
523 const uint16_t zOffset = static_cast<uint16_t>(isOctTree ? localCoord.z * 2 : 0);
524 children.reserve(xOffset * yOffset * (isOctTree ? zOffset : 1));
525
526 const size_t bufSize = section.length;
527 const std::vector<uint8_t>& bitarray = subtree->bitstreams[section.bitstreamIdx];
528
529 for (uint16_t x = xOffset; x <= xOffset + 1; ++x) {
530 for (uint16_t y = yOffset; y <= yOffset + 1; ++y) {
531 for (uint16_t z = zOffset; z <= zOffset + 1; ++z) {
532 const uint32_t mortonIndex = static_cast<uint32_t>(isOctTree ? interleaveBits(x, y, z) : interleaveBits(x, y));
533 const uint32_t levelOffset = static_cast<uint32_t>((std::pow(N, localLevel) - 1) / (N - 1));
534 const uint32_t offset = levelOffset + mortonIndex;
535 const int coarseIdx = offset >> 3; // Divide by 8
536 const int fineIdx = offset & 0b111;
537
538 assert((section.startIdx + coarseIdx) < int(bufSize) && "Index out of bounds");
539 const uint8_t val = bitarray[section.startIdx + coarseIdx];
540 if (val) {
541 std::bitset<8> valBits(val);
542 if (valBits.test(fineIdx)) {
543 int numbits = localLevel;
544 const int32_t gx = concatBits(static_cast<uint16_t>(subtree->globalCoord.x), x, numbits);
545 const int32_t gy = concatBits(static_cast<uint16_t>(subtree->globalCoord.y), y, numbits);
546 const int32_t gz = isOctTree ? concatBits(static_cast<uint16_t>(subtree->globalCoord.z), z, numbits) : -1;
547 OGC3DTiles::Coord childCoord = OGC3DTiles::Coord{ subtree->globalCoord.level + localLevel, gx, gy, gz };
548
549 assert(childCoord.level > parent.level && "Child coord must have a higher level than the parent");
550 children.emplace_back(childCoord);
551 }
552 }
553
554 if (!isOctTree) {
555 break;
556 }
557 }
558 }
559 }
560
561#if 0 // Sanity check
562 // Do we have any duplicates?
563 for (auto c : children) {
564 int count = 0;
565 for (auto x : children) {
566 if (x.toHash() == c.toHash()) ++count;
567 }
568 if (count > 1) {
569 LOG_DEBUG(logger, "ERROR: Node %s is duplicated in child list\n", c.toString().c_str());
570 assert(false);
571 }
572 }
573#endif
574
575 return children;
576}
577
586Cogs::Core::DataFetcherManager::FetchId
587OGC3DTilesSubtree::fetchSubtree(Cogs::Core::Context* context, const std::string& URL, uint32_t uniqueComponentId, FetchSubtreeCallback callback)
588{
589 Cogs::Core::DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, URL,
590 [context, URL, callback, uniqueComponentId](std::unique_ptr<Cogs::FileContents> inData) {
591 if (OGC3DTilesSystem::componentIsStale(context, uniqueComponentId)) {
592 return;
593 }
594
595 Cogs::FileContents* data = inData.get();
596 Subtree* subtree = nullptr;
597 bool success = false;
598
599 if (data && data->data()) {
600 subtree = new Subtree;
601 subtree->pendingRequests = 0;
602 subtree->tileAvailability.allIsAvailable = false;
603 subtree->childSubtreeAvailability.allIsAvailable = false;
604
605 size_t paramPos = URL.find_last_of('?');
606 const std::string fileURL = URL.substr(0, paramPos == std::string::npos ? URL.size() : paramPos);
607
608 Memory::MemoryBuffer contentsBuff = inData->take();
609 if (IO::extension(fileURL) == ".subtree" || IO::extension(fileURL) == ".SUBTREE") {
610 success = parseSubtreeBufferBinary(context, &contentsBuff, subtree, URL, uniqueComponentId);
611 }
612 else if (IO::extension(fileURL) == ".json" || IO::extension(fileURL) == ".JSON") {
613 std::string_view jsonStr = std::string_view(static_cast<const char*>(contentsBuff.data()), contentsBuff.size());
614 success = parseSubtreeBufferJSON(context, jsonStr, subtree, URL, uniqueComponentId);
615 }
616 else {
617 LOG_ERROR(logger, "fetchSubtree(): Unknown subtree extension for '%s'", fileURL.c_str());
618 }
619
620 if (!success) {
621 delete subtree;
622 subtree = nullptr;
623 }
624 }
625 else {
626 LOG_ERROR(logger, "fetchSubtree(): Could not fetch subtree '%s'", URL.c_str());
627 }
628
629 auto finishTask = [callback, subtree]() {
630 callback(subtree);
631 };
632
633#if defined(EMSCRIPTEN)
634 // Cogs.js support - no threading
635 (void)context; // Silence unused capture warning.
636 finishTask();
637#else
638 context->engine->runTaskInMainThread(std::move(finishTask));
639#endif
640 });
641
642 return fetchId;
643}
644
645bool
646checkAvailability(const Subtree* subtree, int bitstreamSectionIdx, int localLevel, int localX, int localY, /*opt*/ int localZ)
647{
648 if (subtree->pendingRequests > 0) {
649 return false;
650 }
651
652 uint64_t mortonIndex = 0;
653 float N = 4.0f;
654 if (localZ >= 0) { // We are in Octree mode
655 N = 8.0f;
656 mortonIndex = static_cast<uint32_t>(interleaveBits(static_cast<uint16_t>(localX), static_cast<uint16_t>(localY), static_cast<uint16_t>(localZ)));
657 }
658 else {
659 mortonIndex = interleaveBits(static_cast<uint16_t>(localX), static_cast<uint16_t>(localY));
660 }
661 assert(mortonIndex < 0xFFFFFFFF && "Internal error. Morton index is way too large.");
662
663 uint32_t levelOffset = static_cast<uint32_t>((std::pow(N, localLevel) - 1) / (N - 1));
664 uint32_t offset = levelOffset + ((uint32_t)mortonIndex);
665
666 assert(bitstreamSectionIdx < int(subtree->bitstreamSections.size()) && "Index out of bounds");
667 const BitstreamSection& section = subtree->bitstreamSections[bitstreamSectionIdx];
668
669 if (section.bitstreamIdx >= subtree->bitstreams.size()) {
670 LOG_ERROR(logger, "checkAvailibility(): Bitstream index > number of available bitstreams (%d vs %d)", section.bitstreamIdx, (int)subtree->bitstreams.size());
671 return false;
672 }
673 assert(section.bitstreamIdx < subtree->bitstreams.size());
674 const size_t bufSize = section.length;
675
676 int coarseIdx = offset >> 3; // Divide by 8
677 int fineIdx = offset & 0b111;
678
679 if (coarseIdx > int(bufSize)) {
680 LOG_ERROR(logger, "checkAvailability idx out of bounds for '%d:%d|%d|%d': %d vs %d", localLevel, localX, localY, localZ, (int)coarseIdx, (int)bufSize);
681 return false;
682 }
683
684 unsigned char val = subtree->bitstreams[section.bitstreamIdx][section.startIdx + coarseIdx];
685 std::bitset<8> valBits(val);
686 return valBits.test(fineIdx);
687}
688
689bool
690OGC3DTilesSubtree::hasContent(const Subtree* subtree, const OGC3DTiles::Coord& coord)
691{
692 const OGC3DTiles::Coord localCoord = globalToLocal(subtree, coord);
693
694 bool hasContent = false;
695 for (const Availability& c : subtree->contentAvailability) {
696 if (c.allIsAvailable) {
697 hasContent = true;
698 break;
699 }
700 else if (c.availableCount > 0) {
701 hasContent = checkAvailability(subtree, c.bitstreamIdx, localCoord.level, localCoord.x, localCoord.y, localCoord.z);
702 if (hasContent) {
703 break;
704 }
705 }
706 }
707
708 return hasContent;
709}
710
718OGC3DTilesSubtree::getSubtreesRootCoord(int levelsPerSubtree, const OGC3DTiles::Coord& coord)
719{
720 int subtreeLevel = coord.level / levelsPerSubtree;
721 int levelsLeft = coord.level % levelsPerSubtree;
722
723 return OGC3DTiles::Coord{ subtreeLevel * levelsPerSubtree,
724 coord.x >> levelsLeft,
725 coord.y >> levelsLeft,
726 coord.z >= 0 ? coord.z >> levelsLeft : -1 };
727}
728
730OGC3DTilesSubtree::globalToLocal(const Subtree* subtree, const OGC3DTiles::Coord& globalCoord)
731{
732 const int relativeLevel = globalCoord.level - subtree->globalCoord.level;
733 return OGC3DTiles::Coord{
734 relativeLevel,
735 globalCoord.x - (subtree->globalCoord.x << relativeLevel),
736 globalCoord.y - (subtree->globalCoord.y << relativeLevel) ,
737 globalCoord.z >= 0 ? globalCoord.z - (subtree->globalCoord.z << relativeLevel) : -1
738 };
739}
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
static bool componentIsStale(Context *context, uint32_t id)
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
constexpr uint64_t mortonCode(uint16_t i, uint16_t j, uint16_t k)
Interleave bits of 3 values to form the 3-way Morton code.
Definition: MortonCode.h:10
Abstract base class storing data read from a file.
Definition: FileContents.h:20