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