Cogs.Core
OGC3DTilesTileset.cpp
1#include "OGC3DTilesTileset.h"
2
3#include "Context.h"
4#include "Engine.h"
5
6#include <Foundation/Logging/Logger.h>
7#include <Serialization/JsonParser.h>
8
9#include <glm/gtc/type_ptr.hpp>
10
15namespace {
16 Cogs::Logging::Log logger = Cogs::Logging::getLogger("OGC3DTilesTileset");
17}
18
19using namespace Cogs::Core;
20using namespace Cogs::Core::OGC3DTilesTileset;
21
23parseBoundingVolumeJSON(const Value& value)
24{
26 bv.type = OGC3DTiles::BoundingVolumeType::UNKNOWN;
27
28 if (!value.IsObject()) {
29 LOG_ERROR(logger, "The 'BoundingVolume' object cannot be an array.");
30 return bv;
31 }
32
33 for (Value::ConstMemberIterator itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
34 std::string key = std::string(itr->name.GetString());
35
36 if (key != "extensions" && !itr->value.IsArray()) {
37 LOG_ERROR(logger, "Value of boundingVolume box/region/sphere is not an array (key=%s)", key.c_str());
38 continue;
39 }
40
41 int i = 0;
42 if (key == "box") {
43 auto array = itr->value.GetArray();
44 if (array.Size() != 12) {
45 LOG_ERROR(logger, "Bounding volume box array does not contain 12 elements");
46 continue;
47 }
48 bv.type = OGC3DTiles::BoundingVolumeType::BOX;
49 for (auto& v : array) {
50 bv.values[i++] = v.GetDouble();
51 }
52 }
53 else if (key == "region") {
54 bv.type = OGC3DTiles::BoundingVolumeType::REGION;
55 auto array = itr->value.GetArray();
56 if (array.Size() != 6) {
57 LOG_ERROR(logger, "Bounding volume region array does not contain 6 elements");
58 continue;
59 }
60 for (auto& v : array) {
61 bv.values[i++] = v.GetDouble();
62 }
63 }
64 else if (key == "sphere") {
65 bv.type = OGC3DTiles::BoundingVolumeType::SPHERE;
66 auto array = itr->value.GetArray();
67 if (array.Size() != 4) {
68 LOG_ERROR(logger, "Bounding volume sphere array does not contain 4 elements");
69 continue;
70 }
71 for (auto& v : array) {
72 bv.values[i++] = v.GetDouble();
73 }
74 }
75 else if (key == "extensions" || key == "extras") {
76 // Ignored for now
77 }
78 else {
79 // Sometimes the "boundingVolume" struct will only contain an "extensions" dict, nothing else.
80 LOG_WARNING(logger, "Unknown BoundingVolume type: '%s'", key.c_str());
81 }
82 }
83
84 return bv;
85}
86
88parseContentJSON(const Value& value)
89{
90 Content content;
91 if (!value.IsObject()) {
92 LOG_ERROR(logger, "The 'Content' object cannot be an array.");
93 return content;
94 }
95
96 for (Value::ConstMemberIterator itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
97 std::string key = std::string(itr->name.GetString());
98 if (key == "uri") {
99 if (!itr->value.IsString()) {
100 LOG_ERROR(logger, "The member 'uri' is not a string");
101 continue;
102 }
103 content.URI = std::string(itr->value.GetString());
104 }
105 else if (key == "group") {
106 if (!itr->value.IsString()) {
107 LOG_ERROR(logger, "The member 'group' is not an integer");
108 continue;
109 }
110 content.group = itr->value.GetInt();
111 }
112 // FIXME: "boundingVolume" for a "content" section is not the same as for a "tile". It is a more snug bbox which
113 // encompass the actual geometry. It is however optional and could contain other meta-data. Ignored for now.
114 /*
115 else if (key == "boundingVolume") {
116 content.boundingVolume = parseBoundingVolumeJSON(itr->value["boundingVolume"]);
117 }
118 */
119 }
120
121 return content;
122}
123
125parseImplicitTilingJSON(const Value& value)
126{
127 ImplicitTiling implicitTiling;
128 if (!value.IsObject()) {
129 LOG_ERROR(logger, "The 'ImplicitTiling' object cannot be an array.");
130 return implicitTiling;
131 }
132
133 for (Value::ConstMemberIterator itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
134 std::string key = std::string(itr->name.GetString());
135 if (key == "subdivisionScheme") {
136 if (!itr->value.IsString()) {
137 LOG_ERROR(logger, "The member 'subdivisionScheme' is not a string");
138 continue;
139 }
140 implicitTiling.isOctTree = std::string(itr->value.GetString()) == "OCTREE";
141 }
142 else if (key == "availableLevels") {
143 if (!itr->value.IsInt()) {
144 LOG_ERROR(logger, "The member 'availableLevels' is not an integer");
145 continue;
146 }
147 implicitTiling.availableLevels = (uint8_t)itr->value.GetInt();
148
149 // FIXME: We should ensure that number of available levels does not exceed our "Morton Index" calculator
150 // ie. 16 bits. => (16-1)=15 levels capacity for quadtrees, less for octtrees.
151 if (implicitTiling.availableLevels > 15) {
152 /*
153 We'll only print a warning here as not all datasets rely on the Avilability bitmap and therefore won't use
154 the Morton-indexing at all.
155 */
156 LOG_WARNING(logger, "The number of levels is %d. When the depth exceeds 15 there might be issues with the indexing.",
157 implicitTiling.availableLevels);
158 }
159 }
160 else if (key == "subtreeLevels") {
161 if (!itr->value.IsInt()) {
162 LOG_ERROR(logger, "The member 'subtreeLevels' is not an integer");
163 continue;
164 }
165 implicitTiling.subtreeLevels = (uint8_t)itr->value.GetInt();
166 }
167 else if (key == "subtrees") {
168 if (!itr->value["uri"].IsString()) {
169 LOG_ERROR(logger, "The member 'uri' is not a string");
170 continue;
171 }
172 implicitTiling.subTreeURLScheme = itr->value["uri"].GetString();
173 }
174 }
175
176 return implicitTiling;
177}
178
182std::vector<Content>
183parseContentsJSON(const Value& value)
184{
185 std::vector<Content> contents;
186
187 if (!value.IsArray()) {
188 LOG_ERROR(logger, "The 'Contents' object must be an array.");
189 return contents;
190 }
191
192 auto array = value.GetArray();
193 contents.reserve(array.Size());
194
195 for (auto& c : array) {
196 contents.emplace_back(parseContentJSON(c));
197 }
198
199 return contents;
200}
201
202Tile
203parseTileJSON(const Value& value)
204{
205 Tile tile;
206 tile.transform = glm::identity<glm::dmat4>();
207 tile.useImplicitTiling = false;
208
209 if (!value.IsObject()) {
210 LOG_ERROR(logger, "The 'Tile' object cannot be an array.");
211 return tile;
212 }
213
214 for (Value::ConstMemberIterator itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
215 std::string key = std::string(itr->name.GetString());
216 if (key == "geometricError") {
217 tile.geometricError = static_cast<float>(itr->value.GetDouble());
218 }
219 else if (key == "refine") {
220 tile.refine = std::string(itr->value.GetString()) == "ADD" ? RefineType::ADD : RefineType::REPLACE;
221 }
222 else if (key == "transform") {
223 auto array = itr->value.GetArray();
224
225 if (array.Size() != 16) {
226 LOG_ERROR(logger, "Transform matrix array does not contain 16 elements");
227 continue;
228 }
229
230 int i = 0;
231 double tmp[16];
232 for (auto& v : array) {
233 tmp[i++] = v.GetDouble();
234 }
235
236 tile.transform = glm::make_mat4(tmp);
237 }
238 else if (key == "boundingVolume") {
239 tile.boundingVolume = parseBoundingVolumeJSON(value["boundingVolume"]);
240 }
241 else if (key == "content") {
242 tile.contents.emplace_back(parseContentJSON(value["content"]));
243 }
244 else if (key == "contents") {
245 //FIXME: It is illegal to have BOTH "content" and "contents". We should check for this.
246 std::vector<Content> contents = parseContentsJSON(value["contents"]);
247 tile.contents.reserve(tile.contents.size() + contents.size());
248 for (Content& c : contents) {
249 tile.contents.emplace_back(c);
250 }
251 }
252 else if (key == "children") {
253 if (!itr->value.IsArray()) {
254 LOG_ERROR(logger, "The 'children' object must be an array.");
255 continue;
256 }
257
258 auto array = itr->value.GetArray();
259 tile.children.reserve(array.Size());
260 for (Value::ConstValueIterator itr2 = array.Begin(); itr2 != array.End(); ++itr2) {
261 Tile childTile = parseTileJSON(*itr2);
262 if (childTile.refine == RefineType::DEFAULT) {
263 childTile.refine = tile.refine; // Inherit value from parent if not specified
264 }
265 tile.children.emplace_back(childTile);
266 }
267 }
268 else if (key == "implicitTiling") { // This part is optional
269 tile.useImplicitTiling = true;
270 tile.implicitTiling = parseImplicitTilingJSON(value["implicitTiling"]);
271 LOG_DEBUG(logger, "Tileset uses implicit tiling (%s)", tile.implicitTiling.isOctTree ? "octtree" : "quadtree");
272 }
273 }
274
275 return tile;
276}
277
278//
279// ============================================================================================================
280//
281
282bool
283parseJSON(std::string_view json, Tileset* tileset)
284{
285 Document doc = parseJson(json, JsonParseFlags::None);
286
287 if (doc.HasMember("asset")) {
288 GenericObject asset = doc["asset"].GetObject();
289 tileset->version = asset["version"].GetString();
290
291 if (tileset->version == "1.0") {
292 LOG_WARNING(logger, "Tileset is version 1.0 which is not fully tested. Upgrade to 1.1 if possible");
293 }
294
295 if (asset.HasMember("gltfUpAxis")) {
296 LOG_WARNING(logger, "Tileset is using the unofficial/unsupported 'gltfUpAxis' setting ('gltfUpAxis'=%s).", asset["gltfUpAxis"].GetString());
297 }
298 }
299 else {
300 LOG_ERROR(logger, "No geometricError asset in tileset");
301 return false;
302 }
303
304 if (doc.HasMember("root")) {
305 tileset->root = parseTileJSON(doc["root"].GetObject());
306 }
307 else {
308 LOG_ERROR(logger, "No root attribute in tileset");
309 return false;
310 }
311
312 tileset->geometricError = 0.0;
313 if (doc.HasMember("geometricError")) {
314 tileset->geometricError = doc["geometricError"].GetFloat();
315 }
316
317 return true;
318}
319
324Cogs::Core::DataFetcherManager::FetchId
325OGC3DTilesTileset::fetch(Context* context, const std::string& URL, FetchCallback callback)
326{
327 LOG_INFO(logger, "Fetching tileset: '%s'", URL.c_str());
328 Cogs::Core::DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, URL,
329 [context, callback, URL](std::unique_ptr<Cogs::FileContents> data) {
330 Tileset* tileset = nullptr;
331 if (data.get()) {
332 // Data is available in worker thread. Create a data parsing task lambda
333 Cogs::Memory::MemoryBuffer contentsBuff = data->take();
334 std::string_view jsonStr = std::string_view(static_cast<const char*>(contentsBuff.data()), contentsBuff.size());
335
336 tileset = new Tileset;
337 bool ok = parseJSON(jsonStr, tileset);
338 if (!ok) {
339 LOG_ERROR(logger, "Error parsing tileset");
340 delete tileset;
341 tileset = nullptr;
342 }
343 }
344 else {
345 LOG_ERROR(logger, "Error fetching tileset from '%s'", URL.c_str());
346 }
347
348 auto finishTask = [callback, tileset]() {
349 callback(tileset);
350 };
351
352#if defined(EMSCRIPTEN)
353 // Cogs.js support - no threading
354 (void)context; // Silence unused capture warning.
355 finishTask();
356#else
357 context->engine->runTaskInMainThread(std::move(finishTask));
358#endif
359 });
360
361 return fetchId;
362}
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
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