Cogs.Core
ParseMetadataFile.cpp
1#include "Serialization/JsonParser.h"
2#include "PotreeSystem.h"
3
4#include "Foundation/Logging/Logger.h"
5#include "Foundation/Platform/IO.h"
6
7namespace {
8 using namespace Cogs::Core;
10
11 bool parseStringMemberNoLog(const char*& value, rapidjson::Value& item, const char* name)
12 {
13 if (auto it = item.FindMember(name); it != item.MemberEnd() && it->value.IsString()) {
14 value = it->value.GetString();
15 return true;
16 }
17 return false;
18 }
19
20 bool parseUIntMemberNoLog(unsigned& value, rapidjson::Value& item, const char* name)
21 {
22 if (auto it = item.FindMember(name); it != item.MemberEnd() && it->value.IsUint()) {
23 value = it->value.GetUint();
24 return true;
25 }
26 return false;
27 }
28
29 bool parseFloatMemberNoLog(float& value, rapidjson::Value& item, const char* name)
30 {
31 if (auto it = item.FindMember(name); it != item.MemberEnd() && it->value.IsNumber()) {
32 value = it->value.GetFloat();
33 return true;
34 }
35 return false;
36 }
37
38 bool parseVec3NoLog(glm::vec3& value, rapidjson::Value& item, const char* name)
39 {
40 if (auto kt = item.FindMember(name); kt != item.MemberEnd()) {
41 if (!kt->value.IsArray()) { return false; }
42 auto arr = kt->value.GetArray();
43 if (arr.Size() != 3) { return false; }
44 for (int i = 0; i < 3; i++) {
45 if (!arr[i].IsNumber()) { return false; }
46 value[i] = arr[i].GetFloat();
47 }
48 return true;
49 }
50 return false;
51 }
52
53 bool parseVec3NoLog(glm::dvec3& value, rapidjson::Value& item, const char* name)
54 {
55 if (auto kt = item.FindMember(name); kt != item.MemberEnd()) {
56 if (!kt->value.IsArray()) { return false; }
57 auto arr = kt->value.GetArray();
58 if (arr.Size() != 3) { return false; }
59 for (int i = 0; i < 3; i++) {
60 if (!arr[i].IsNumber()) { return false; }
61 value[i] = arr[i].GetDouble();
62 }
63 return true;
64 }
65 return false;
66 }
67
68 bool parseBBox(PotreeData* poData, glm::dvec3& bbmin, glm::dvec3& bbmax, Document& doc, const std::string& name, uint32_t instanceId)
69 {
70 if (auto jt = doc.FindMember(name.c_str()); jt != doc.MemberEnd()) {
71 if (jt->value.IsObject()) {
72 auto& parent = jt->value;
73 if (poData->versionMajor == 1) {
74 struct { double* dst; const char* name; }
75 components[] = {
76 {&bbmin.x, "lx"}, {&bbmin.y, "ly"},{&bbmin.z, "lz"},
77 {&bbmax.x, "ux"}, {&bbmax.y, "uy"},{&bbmax.z, "uz"},
78 };
79 for (auto& component : components) {
80 if (auto it = parent.FindMember(component.name); it != parent.MemberEnd() && it->value.IsNumber()) {
81 *component.dst = it->value.GetDouble();
82 }
83 else {
84 LOG_ERROR(logger, "[instance=%u] metadata file: %s element of %s is missing or not a numbers.", instanceId, component.name, name.c_str());
85 return false;
86 }
87 }
88 }
89 else {
90 if (!parseVec3NoLog(bbmin, parent, "min")) {
91 LOG_ERROR(logger, "[instance=%u] metadata file: min element of %s is missing or not an array of three numbers.", instanceId, name.c_str());
92 return false;
93 }
94 if (!parseVec3NoLog(bbmax, parent, "max")) {
95 LOG_ERROR(logger, "[instance=%u] metadata file: min element of %s is missing or not an array of three numbers.", instanceId, name.c_str());
96 return false;
97 }
98 }
99
100 return true;
101 }
102 else {
103 LOG_ERROR(logger, "[instance=%u] metadata file: %s is not an object", instanceId, name.c_str());
104 return false;
105 }
106 }
107 else {
108 LOG_ERROR(logger, "[instance=%u] metadata file: missing %s", instanceId, name.c_str());
109 return false;
110 }
111 }
112
113 bool parsePointAttributeName(PotreeAttributes& attribute, const char* str, uint32_t instanceId)
114 {
115 switch (Cogs::StringView(str).hash()) {
116 case Cogs::hash("POSITION_CARTESIAN"):
117 attribute = PotreeAttributes::POSITION_CARTESIAN;
118 break;
119 case Cogs::hash("RGBA"):
120 case Cogs::hash("RGBA_PACKED"):
121 case Cogs::hash("COLOR_PACKED"):
122 attribute = PotreeAttributes::RGBA_PACKED;
123 break;
124 case Cogs::hash("RGB_PACKED"):
125 attribute = PotreeAttributes::RGB_U8_PACKED;
126 break;
127 case Cogs::hash("NORMAL"):
128 case Cogs::hash("NORMAL_FLOATS"):
129 attribute = PotreeAttributes::NORMAL;
130 break;
131 case Cogs::hash("NORMAL_SPHEREMAPPED"):
132 attribute = PotreeAttributes::NORMAL_SPHEREMAPPED;
133 break;
134 case Cogs::hash("NORMAL_OCT16"):
135 attribute = PotreeAttributes::NORMAL_OCT16;
136 break;
137 case Cogs::hash("INTENSITY"):
138 case Cogs::hash("intensity"):
139 attribute = PotreeAttributes::INTENSITY_U16;
140 break;
141 case Cogs::hash("CLASSIFICATION"):
142 attribute = PotreeAttributes::CLASSIFICATION;
143 break;
144 case Cogs::hash("FILLER_1B"):
145 attribute = PotreeAttributes::FILLER_1B;
146 break;
147 default:
148 LOG_WARNING(logger, "[instance=%u] metadata file: unrecoginzed element name %s", instanceId, str);
149 return false;
150 }
151 return true;
152 }
153
154 bool parseAttributeItem17(PotreeData* poData, PotreeAttributes& attribute, unsigned& skipbytes, rapidjson::Value& item, uint32_t instanceId)
155 {
156 assert(poData->versionMajor == 1 && poData->versionMinor < 8);
157 skipbytes = 0;
158 attribute = PotreeAttributes::COUNT;
159 if (!item.IsString()) {
160 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttributes element is not a string", instanceId);
161 return false;
162 }
163 if (!parsePointAttributeName(attribute, item.GetString(), instanceId)) return false;
164 assert(attribute < PotreeAttributes::COUNT);
165 return true;
166 }
167
168 bool parseAttributeItem18(PotreeData* poData, PotreeAttributes& attribute, unsigned& skipbytes, rapidjson::Value& item, uint32_t instanceId)
169 {
170 assert(poData->versionMajor == 1 && 8 <= poData->versionMinor);
171 skipbytes = 0;
172 attribute = PotreeAttributes::COUNT;
173
174 if (!item.IsObject()) {
175 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttributes element is not an object", instanceId);
176 return false;
177 }
178
179 const char* name = nullptr;
180 if (auto nn = item.FindMember("name"); nn != item.MemberEnd()) {
181 if (nn->value.IsString()) {
182 name = nn->value.GetString();
183 if (!parsePointAttributeName(attribute, name, instanceId)) {
184
185 // If 1.8 <= version, we know the number of bytes this element has, and can replace it with filler bytes.
186 if (auto it = item.FindMember("size"); it != item.MemberEnd()) {
187 if (it->value.IsUint()) {
188 skipbytes = it->value.GetUint();
189 }
190 }
191 return false;
192
193 }
194 }
195 else {
196 LOG_ERROR(logger, "[instance=%u] metadata file: attribute name field is not a string", instanceId);
197 return false;
198 }
199 }
200 else {
201 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute element missing name", instanceId);
202 return false;
203 }
204
205 // Check that layout is as we expect
206 struct {
207 const char* key;
208 size_t value;
209 bool isString;
210 } expected[4] = {
211 {"size", 0, false},
212 {"elements", 0, false},
213 {"elementSize", 0, false},
214 {"type", 0, true}
215 };
216 switch (attribute) {
217 case PotreeAttributes::POSITION_CARTESIAN: expected[1].value = 3; expected[2].value = 4; expected[3].value = Cogs::hash("int32"); break;
218 case PotreeAttributes::RGBA_PACKED: expected[1].value = 4; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
219 case PotreeAttributes::RGB_U8_PACKED: expected[1].value = 3; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
220 case PotreeAttributes::NORMAL: expected[1].value = 3; expected[2].value = 4; expected[3].value = Cogs::hash("float"); break;
221 case PotreeAttributes::NORMAL_SPHEREMAPPED: expected[1].value = 2; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
222 case PotreeAttributes::NORMAL_OCT16: expected[1].value = 2; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
223 case PotreeAttributes::INTENSITY_U16: expected[1].value = 1; expected[2].value = 2; expected[3].value = Cogs::hash("uint16"); break;
224 case PotreeAttributes::CLASSIFICATION: expected[1].value = 1; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
225 case PotreeAttributes::FILLER_1B: expected[1].value = 1; expected[2].value = 1; expected[3].value = Cogs::hash("uint8"); break;
226 default:
227 assert(false && "Unhandled attribute enum value");
228 break;
229 }
230 expected[0].value = expected[1].value * expected[2].value;
231 for (const auto& e : expected) {
232 if (auto spec = item.FindMember(e.key); spec != item.MemberEnd()) {
233 if (e.isString) {
234 if (spec->value.IsString()) {
235 if (Cogs::StringView(spec->value.GetString()).hash() != e.value) {
236 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute %s %s: unexpected type %s", instanceId, name, e.key, spec->value.GetString());
237 return false;
238 }
239 }
240 else {
241 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute %s %s is not a string", instanceId, name, e.key);
242 return false;
243 }
244 }
245 else {
246 if (spec->value.IsUint()) {
247 if (spec->value.GetUint() != e.value) {
248 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute %s %s: expected %u, got %u", instanceId, name, e.key, unsigned(e.value), spec->value.GetUint());
249 return false;
250 }
251 }
252 else {
253 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute %s %s is not an unsigned number", instanceId, name, e.key);
254 return false;
255 }
256 }
257 }
258 else {
259 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttribute %s missing %s", instanceId, name, e.key);
260 return false;
261 }
262 }
263
264 assert(attribute < PotreeAttributes::COUNT);
265 return true;
266 }
267
268
269 bool parseAttributeItem20(PotreeData* poData, PotreeAttributes& attribute, unsigned& skipbytes, rapidjson::Value& item, uint32_t instanceId)
270 {
271 assert(poData->versionMajor == 2);
272
273 skipbytes = 0;
274 attribute = PotreeAttributes::COUNT;
275 if (!item.IsObject()) {
276 LOG_ERROR(logger, "[instance=%u] metadata file: pointAttributes element is not an object", instanceId);
277 return false;
278 }
279
280 const char* name = nullptr;
281 if (!parseStringMemberNoLog(name, item, "name")) {
282 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed name element in attribute list", instanceId);
283 return false;
284 }
285
286 unsigned size = 0;
287 if (!parseUIntMemberNoLog(size, item, "size")) {
288 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed size element in attribute %s", instanceId, name);
289 return false;
290 }
291
292 unsigned numElements = 0;
293 if (!parseUIntMemberNoLog(numElements, item, "numElements")) {
294 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed numElements element in attribute %s", instanceId, name);
295 return false;
296 }
297 if (4 < numElements) {
298 LOG_ERROR(logger, "[instance=%u] metadata file: attribute %s has %u elements, more than max supported of 4", instanceId, name, numElements);
299 return false;
300 }
301
302 PotreeeType type = PotreeeType::None;
303 unsigned typeSize = 0;
304 if (auto it = item.FindMember("type"); it != item.MemberEnd() && it->value.IsString()) {
305 switch (Cogs::StringView(it->value.GetString(), it->value.GetStringLength()).hash()) {
306 case Cogs::hash("double"): type = PotreeeType::Double; typeSize = 8; break;
307 case Cogs::hash("float"): type = PotreeeType::Float; typeSize = 4; break;
308 case Cogs::hash("int8"): type = PotreeeType::Int8; typeSize = 1; break;
309 case Cogs::hash("uint8"): type = PotreeeType::UInt8; typeSize = 1; break;
310 case Cogs::hash("int16"): type = PotreeeType::Int16; typeSize = 2; break;
311 case Cogs::hash("uint16"): type = PotreeeType::UInt16; typeSize = 2; break;
312 case Cogs::hash("int32"): type = PotreeeType::Int32; typeSize = 4; break;
313 case Cogs::hash("uint32"): type = PotreeeType::UInt32; typeSize = 4; break;
314 case Cogs::hash("int64"): type = PotreeeType::Int64; typeSize = 8; break;
315 case Cogs::hash("uint64"): type = PotreeeType::UInt64; typeSize = 8; break;
316 default:
317 LOG_ERROR(logger, "[instance=%u] metadata file: unrecognized attribute type '%s' attribute %s", instanceId, it->value.GetString(), name);
318 return false;
319 }
320 }
321 else {
322 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed type element in attribute %s", instanceId, name);
323 return false;
324 }
325
326 unsigned elementSize = 0;
327 if (!parseUIntMemberNoLog(elementSize, item, "elementSize")) {
328 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed elementSize element in attribute %s", instanceId, name);
329 return false;
330 }
331 if (typeSize != elementSize) {
332 LOG_ERROR(logger, "[instance=%u] metadata file: mismatch type size in attribute %s, expected %u, but got %u", instanceId, name, typeSize, elementSize);
333 return false;
334 }
335 if (typeSize * numElements != size) {
336 LOG_ERROR(logger, "[instance=%u] metadata file: attribute %s: unexpected element size %u, expected %u", instanceId, name, size, typeSize * numElements);
337 return false;
338 }
339
340 double attributeMin[4] = {};
341 if (auto it = item.FindMember("min"); it != item.MemberEnd() && it->value.IsArray() && it->value.GetArray().Size() == numElements) {
342 for (unsigned i = 0; i < numElements; i++) {
343 if (auto& element = it->value.GetArray()[i]; element.IsNumber()) {
344 attributeMin[i] = element.GetDouble();
345 }
346 else {
347 LOG_ERROR(logger, "[instance=%u] metadata file: item %u in min element in attribute %s is not a number", instanceId, i, name);
348 return false;
349 }
350 }
351 }
352 else {
353 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed min element in attribute %s", instanceId, name);
354 return false;
355 }
356
357 double attributeMax[4] = {};
358 if (auto it = item.FindMember("max"); it != item.MemberEnd() && it->value.IsArray() && it->value.GetArray().Size() == numElements) {
359 for (unsigned i = 0; i < numElements; i++) {
360 if (auto& element = it->value.GetArray()[i]; element.IsNumber()) {
361 attributeMax[i] = element.GetDouble();
362 }
363 else {
364 LOG_ERROR(logger, "[instance=%u] metadata file: item %u in max element in attribute %s is not a number", instanceId, i, name);
365 return false;
366 }
367 }
368 }
369 else {
370 LOG_ERROR(logger, "[instance=%u] metadata file: missing or malformed max element in attribute %s", instanceId, name);
371 return false;
372 }
373
374 switch (Cogs::StringView(name).hash()) {
375 case Cogs::hash("position"):
376 if (type == PotreeeType::Int32 && numElements == 3) {
377 attribute = PotreeAttributes::POSITION_CARTESIAN;
378 for (unsigned i = 0; i < 3; i++) { poData->metadata.tbmin[i] = attributeMin[i]; }
379 for (unsigned i = 0; i < 3; i++) { poData->metadata.tbmax[i] = attributeMax[i]; }
380 }
381 break;
382 case Cogs::hash("intensity"):
383 if (type == PotreeeType::UInt16 && numElements == 1) {
384 attribute = PotreeAttributes::INTENSITY_U16;
385 }
386 break;
387 case Cogs::hash("rgb"):
388 if (type == PotreeeType::UInt8 && numElements == 3) {
389 attribute = PotreeAttributes::RGB_U8_PACKED;
390 }
391 else if (type == PotreeeType::UInt16 && numElements == 3) {
392 attribute = PotreeAttributes::RGB_U16_PACKED;
393 }
394 break;
395 default:
396 break;
397 }
398
399 if(attribute == PotreeAttributes::COUNT) {
400 LOG_WARNING(logger, "[instance=%u] metadata file: Unrecognized attribute %s, skipping %u bytes", instanceId, name, size);
401 skipbytes = size;
402 return false;
403 }
404
405 return true;
406 }
407
408 bool parseAttributeItem(PotreeData* poData, PotreeAttributes& attribute, unsigned& skipbytes, rapidjson::Value& item, uint32_t instanceId)
409 {
410 if (poData->versionMajor == 1 && poData->versionMinor < 8) {
411 return parseAttributeItem17(poData, attribute, skipbytes, item, instanceId);
412 }
413 else if (poData->versionMajor == 1) {
414 return parseAttributeItem18(poData, attribute, skipbytes, item, instanceId);
415 }
416 else {
417 return parseAttributeItem20(poData, attribute, skipbytes, item, instanceId);
418 }
419 }
420
421 bool parsePointAttributes(PotreeData* poData, rapidjson::Document& doc, uint32_t instanceId)
422 {
423 const char* attributeName = poData->versionMajor == 1 ? "pointAttributes" : "attributes";
424
425 if (auto it = doc.FindMember(attributeName); it != doc.MemberEnd()) {
426
427 if (!it->value.IsArray()) {
428 LOG_ERROR(logger, "[instance=%u] metadata file: %s is not an array.", instanceId, attributeName);
429 return false;
430 }
431
432 bool hasPosition = false;
433 poData->hasColor = false;
434 poData->hasNormal = false;
435 poData->hasIntensity = false;
436 poData->hasClassification = false;
437 for (auto& subitem : it->value.GetArray()) {
438 unsigned skipbytes = 0;
439 PotreeAttributes attribute = PotreeAttributes::COUNT;
440 if (!parseAttributeItem(poData, attribute, skipbytes, subitem, instanceId)) {
441
442 if (skipbytes) {
443 LOG_WARNING(logger, "[instance=%u] Failed to parse attribute, skipping %u bytes", instanceId, skipbytes);
444 for (unsigned i = 0; i < skipbytes; i++) {
445 poData->attributes.push_back(PotreeAttributes::FILLER_1B);
446 }
447 }
448 else {
449 LOG_ERROR(logger, "[instance=%u] Failed to parse attribute", instanceId);
450 return false;
451 }
452 }
453 else {
454 switch (attribute) {
455 case PotreeAttributes::POSITION_CARTESIAN:
456 if (hasPosition) {
457 LOG_ERROR(logger, "[instance=%u] metadata file: Multiple position sources", instanceId);
458 return false;
459 }
460 hasPosition = true;
461 break;
462 case PotreeAttributes::RGBA_PACKED:
463 case PotreeAttributes::RGB_U8_PACKED:
464 case PotreeAttributes::RGB_U16_PACKED:
465 if (poData->hasColor) {
466 LOG_ERROR(logger, "[instance=%u] metadata file: Multiple color sources", instanceId);
467 return false;
468 }
469 poData->hasColor = true;
470 break;
471 case PotreeAttributes::NORMAL:
472 poData->hasNormal = true;
473 break;
474 case PotreeAttributes::NORMAL_SPHEREMAPPED:
475 poData->hasNormal = true;
476 break;
477 case PotreeAttributes::NORMAL_OCT16:
478 poData->hasNormal = true;
479 break;
480 case PotreeAttributes::INTENSITY_U16:
481 if (poData->hasIntensity) {
482 LOG_ERROR(logger, "[instance=%u] metadata file: Multiple intensity sources", instanceId);
483 return false;
484 }
485 poData->hasIntensity = true;
486 break;
487 case PotreeAttributes::CLASSIFICATION:
488 poData->hasClassification = true;
489 break;
490 case PotreeAttributes::FILLER_1B:
491 break;
492 default:
493 assert(false && "Unhandled attribute enum value");
494 break;
495 }
496 poData->attributes.push_back(attribute);
497 }
498 }
499
500 if (!hasPosition) {
501 LOG_ERROR(logger, "[instance=%u] metadata file: %s does not contain a position element", instanceId, attributeName);
502 return false;
503 }
504
505 // Figure out vertex laout to use.
506 poData->layout = static_cast<PotreeVertexLayout>((poData->hasColor ? 1 : 0) |
507 (poData->hasNormal ? 2 : 0) |
508 (poData->hasIntensity ? 4 : 0) |
509 (poData->hasClassification ? 8 : 0));
510
511 const PotreeVertexLayoutInfo& info = PotreeSystem::vertexLayoutInfo[size_t(poData->layout)];
512 if (info.stride != ~0u) {
513 poData->streamsLayout.vertexFormats[0] = Cogs::VertexFormats::createVertexFormat(info.elements.data(), info.elements.size());
514 poData->streamsLayout.numStreams = 1;
515 poData->streamsLayout.updateHash();
516 }
517 else {
518 LOG_ERROR(logger, "[instance=%u] metadata file: Unimplemented vertex format: color=%s normal=%s intensity=%s classification=%s",
519 instanceId,
520 poData->hasColor ? "yes" : "no",
521 poData->hasNormal ? "yes" : "no",
522 poData->hasIntensity ? "yes" : "no",
523 poData->hasClassification ? "yes" : "no");
524 return false;
525 }
526 }
527 else {
528 LOG_ERROR(logger, "[instance=%u] metadata file: Missing %s", instanceId, attributeName);
529 return false;
530 }
531
532 return true;
533 }
534
535}
536
537namespace Cogs::Core
538{
539
540 bool parseCloudJs(Context* /*context*/, PotreeData* poData, const Cogs::FileContents* data)
541 {
542
543 Document doc = parseJson(StringView((const char*)data->ptr, data->size), JsonParseFlags::None);
544
545 if (doc.HasParseError()) {
546 LOG_ERROR(logger, "[instance=%u] metadata file: Failed to parse JSON", poData->instanceId);
547 return false;
548 }
549
550 if (!doc.IsObject()) {
551 LOG_ERROR(logger, "[instance=%u] metadata file: JSON root is not an object", poData->instanceId);
552 return false;
553 }
554
555 if (auto it = doc.FindMember("version"); it != doc.MemberEnd()) {
556 unsigned versionMajor = 0;
557 unsigned versionMinor = 0;
558 if (it->value.IsString()) {
559
560 auto str_N = static_cast<size_t>(it->value.GetStringLength());
561 auto * str = it->value.GetString();
562
563 size_t i = 0;
564 size_t j = 0;
565 for (j = i; j < str_N; j++) {
566 if ('0' <= str[j] && str[j] <= '9') versionMajor = 10 * versionMajor + (str[j] - '0');
567 else break;
568 }
569 if (i == j) {
570 LOG_ERROR(logger, "[instance=%u] metadata file: version \"%s\": missing major version number", poData->instanceId, str);
571 return false;
572 }
573 if (j == str_N || str[j] != '.') {
574 LOG_ERROR(logger, "[instance=%u] metadata file: version \"%s\": no dot after major version number", poData->instanceId, str);
575 }
576 i = j + 1;
577 for (j = i; j < str_N; j++) {
578 if ('0' <= str[j] && str[j] <= '9') versionMinor = 10 * versionMinor + (str[j] - '0');
579 else break;
580 }
581 if (i == j) {
582 LOG_ERROR(logger, "[instance=%u] metadata file: version \"%s\": missing minor version number", poData->instanceId, str);
583 return false;
584 }
585
586 LOG_DEBUG(logger, "[instance=%u] metadata file: detected version %u.%u", poData->instanceId, versionMajor, versionMinor);
587 poData->versionMajor = versionMajor;
588 poData->versionMinor = versionMinor;
589 }
590 else {
591 LOG_ERROR(logger, "[instance=%u] metadata file: version is not a string", poData->instanceId);
592 return false;
593 }
594 }
595 else {
596 LOG_ERROR(logger, "[instance=%u] metadata file: version is missing", poData->instanceId);
597 return false;
598 }
599
600 if (poData->versionMajor == 1) {
601 if (auto it = doc.FindMember("octreeDir"); it != doc.MemberEnd()) {
602 if (it->value.IsString()) {
603 std::string path = poData->rootPath;
604 if (!path.empty() && path.back() != '/') { path.push_back('/'); }
605 path.append(it->value.GetString());
606 if (!path.empty() && path.back() != '/') { path.push_back('/'); }
607 poData->octreeDir = std::move(path);
608 }
609 else {
610 LOG_ERROR(logger, "metadata file: octreeDir is not a string");
611 return false;
612 }
613 }
614 else {
615 LOG_ERROR(logger, "metadata file: Missing octreeDir");
616 return false;
617 }
618 }
619
620 if (auto it = doc.FindMember("suffix"); it != doc.MemberEnd()) {
621 if (it->value.IsString()) {
622 poData->suffix = it->value.GetString();
623 switch (StringView(it->value.GetString(), it->value.GetStringLength()).hashLowercase()) {
624 case Cogs::hash(".bin"):
625 poData->encoding = PotreeEnconding::Default;
626 break;
627 case Cogs::hash(".bin.zst"):
628 case Cogs::hash(".bin.zstd"):
629 poData->encoding = PotreeEnconding::ZStd;
630 break;
631 default:
632 poData->encoding = PotreeEnconding::Default;
633 LOG_WARNING(logger, "Unrecognized suffix '%s', assuming non-compressed data.", it->value.GetString());
634 break;
635 }
636 }
637 else {
638 LOG_ERROR(logger, "metadata file: suffix is not a string");
639 return false;
640 }
641 }
642
643 if (!parseBBox(poData, poData->metadata.bbmin, poData->metadata.bbmax, doc, "boundingBox", poData->instanceId)) return false;
644 if (poData->versionMajor == 1) {
645 if (!parseBBox(poData, poData->metadata.tbmin, poData->metadata.tbmax, doc, "tightBoundingBox", poData->instanceId)) return false;
646 }
647
648
649 poData->attributes.clear();
650 if (!parsePointAttributes(poData, doc, poData->instanceId)) return false;
651
652 // version 1.x specifics
653 if (poData->versionMajor == 1) {
654 if (!parseUIntMemberNoLog(poData->hierarchyStepSize, doc, "hierarchyStepSize")) {
655 LOG_ERROR(logger, "metadata file: hierarchyStepSize is missing or not an unsigned integer");
656 return false;
657 }
658 float scale = 1.f;
659 if (!parseFloatMemberNoLog(scale, doc, "scale")) {
660 LOG_ERROR(logger, "metadata file: scale is missing or not a number");
661 return false;
662 }
663 poData->metadata.scale = glm::vec3(scale);
664
665 }
666
667 // version 2.x specifics
668 else if (poData->versionMajor == 2) {
669
670 // Hierarchy
671 if (auto it = doc.FindMember("hierarchy"); it != doc.MemberEnd() && it->value.IsObject()) {
672 auto& item = it->value;
673 if (!parseUIntMemberNoLog(poData->firstChunkSize, item, "firstChunkSize")) {
674 LOG_ERROR(logger, "metadata file: missing or malformed firstChunkSize in hierarchy");
675 return false;
676 }
677 if (!parseUIntMemberNoLog(poData->hierarchyStepSize, item, "stepSize")) {
678 LOG_ERROR(logger, "metadata file: missing or malformed stepSize in hierarchy");
679 return false;
680 }
681 if (!parseUIntMemberNoLog(poData->hierarchyDepth, item, "depth")) {
682 LOG_ERROR(logger, "metadata file: missing or malformed depth in hierarchy");
683 return false;
684 }
685 }
686 else {
687 LOG_ERROR(logger, "metadata file: hierarchy is missing");
688 return false;
689 }
690
691 // encoding
692 const char* encoding = nullptr;
693 if (!parseStringMemberNoLog(encoding, doc, "encoding")) {
694 LOG_ERROR(logger, "metadata file: encoding is missing or not a string");
695 return false;
696 }
697 switch (StringView(encoding).hash()) {
698 case Cogs::hash("DEFAULT"):
699 poData->encoding = PotreeEnconding::Default;
700 break;
701 case Cogs::hash("BROTLI"):
702 poData->encoding = PotreeEnconding::Brotli;
703 break;
704 default:
705 LOG_ERROR(logger, "metadata file: unrecognized encodng '%s'", encoding);
706 return false;
707 }
708
709 if (!parseVec3NoLog(poData->metadata.offset, doc, "offset")) {
710 LOG_ERROR(logger, "metadata file: offset missing or not an array of 3 numbers");
711 return false;
712 }
713 if (!parseVec3NoLog(poData->metadata.scale, doc, "scale")) {
714 LOG_ERROR(logger, "metadata file: offset missing or not an array of 3 numbers");
715 return false;
716 }
717
718
719 }
720 else {
721 assert(false && "Unsupported version");
722 }
723
724
725 if (8 < poData->hierarchyStepSize) {
726 // We use uint8 to encode cell hierarchy
727 LOG_ERROR(logger, "metadata file: hierarchyStepSize larger than 8 is not supported");
728 return false;
729 }
730
731
732
733 if (auto it = doc.FindMember("spacing"); it != doc.MemberEnd()) {
734
735 if (it->value.IsNumber()) {
736 poData->spacing = it->value.GetFloat();
737 }
738 else {
739 LOG_ERROR(logger, "metadata file: spacing is not a number");
740 return false;
741 }
742 }
743 else {
744 LOG_ERROR(logger, "metadata file: spacing is missing");
745 return false;
746 }
747
748 // FIXME: shoud this end up in transform.coordinate to mitigate large coords issues?
749 poData->octtreeFrame.positionInEntityFrame = 0.5 * (poData->metadata.tbmin + poData->metadata.tbmax);
750 poData->octtreeFrame.toBBoxShift = glm::vec3(poData->metadata.bbmin - poData->octtreeFrame.positionInEntityFrame);
751
752 poData->octtreeFrame.fullBBoxSize = glm::vec3(poData->metadata.bbmax - poData->metadata.bbmin);
753 poData->octtreeFrame.fullBBoxDiagonal = glm::length(poData->octtreeFrame.fullBBoxSize);
754
755 poData->octtreeFrame.tightBBoxMin = glm::vec3(poData->metadata.tbmin - poData->octtreeFrame.positionInEntityFrame);
756 poData->octtreeFrame.tightBBoxMax = glm::vec3(poData->metadata.tbmax - poData->octtreeFrame.positionInEntityFrame);
757
758 poData->octtreeFrame.fullBBoxMin = glm::vec3(poData->metadata.bbmin - poData->octtreeFrame.positionInEntityFrame);
759 poData->octtreeFrame.fullBBoxMax = glm::vec3(poData->metadata.bbmax - poData->octtreeFrame.positionInEntityFrame);
760
761 // Set up point position decoding
762 if (poData->versionMajor == 1) {
763 poData->positionDecode.scale = poData->versionMinor <= 3 ? glm::vec3(1.f) : poData->metadata.scale;
764 poData->positionDecode.shift = glm::vec3(poData->metadata.bbmin - poData->octtreeFrame.positionInEntityFrame);
765 }
766 else if (poData->versionMajor == 2) {
767 poData->positionDecode.scale = poData->metadata.scale;
768 poData->positionDecode.shift = glm::vec3(poData->metadata.offset - poData->octtreeFrame.positionInEntityFrame);
769 }
770 else {
771 LOG_FATAL(logger, "Unsupported major version %d", poData->versionMajor);
772 return false;
773 }
774
775 return true;
776 }
777
778}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
Definition: StringView.cpp:13
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:200
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
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
VertexFormatHandle vertexFormats[maxStreams]
COGSCORE_DLL_API void updateHash()
std::string octreeDir
1.x only: subdir with data
Definition: PotreeSystem.h:213
std::string rootPath
URL to folder containing metadata file.
Definition: PotreeSystem.h:212
Abstract base class storing data read from a file.
Definition: FileContents.h:20