cesium-native 0.44.2
Loading...
Searching...
No Matches
MetadataConversions.h
1#pragma once
2
3#include <CesiumGltf/PropertyTypeTraits.h>
4#include <CesiumUtility/JsonValue.h>
5
6#include <glm/common.hpp>
7#include <glm/gtx/string_cast.hpp>
8
9#include <cerrno>
10#include <cstdint>
11#include <optional>
12#include <string>
13#include <string_view>
14
15namespace CesiumGltf {
20template <typename TTo, typename TFrom, typename Enable = void>
26 static std::optional<TTo> convert(TFrom /*from*/) { return std::nullopt; }
27};
28
32template <typename T> struct MetadataConversions<T, T> {
37 static std::optional<T> convert(T from) { return from; }
38};
39
40#pragma region Conversions to boolean
44template <typename TFrom>
46 bool,
47 TFrom,
48 std::enable_if_t<CesiumGltf::IsMetadataScalar<TFrom>::value>> {
55 static std::optional<bool> convert(TFrom from) {
56 return from != static_cast<TFrom>(0);
57 }
58};
59
63template <> struct MetadataConversions<bool, std::string_view> {
64private:
65 static bool
66 isEqualCaseInsensitive(const std::string_view& a, const std::string_view& b) {
67 if (a.size() != b.size()) {
68 return false;
69 }
70
71 for (size_t i = 0; i < a.size(); i++) {
72 if (std::tolower(a[i]) != std::tolower(b[i])) {
73 return false;
74 }
75 }
76 return true;
77 }
78
79public:
89 static std::optional<bool> convert(const std::string_view& from) {
90 if (isEqualCaseInsensitive(from, "1") ||
91 isEqualCaseInsensitive(from, "true") ||
92 isEqualCaseInsensitive(from, "yes")) {
93 return true;
94 }
95
96 if (isEqualCaseInsensitive(from, "0") ||
97 isEqualCaseInsensitive(from, "false") ||
98 isEqualCaseInsensitive(from, "no")) {
99 return false;
100 }
101
102 return std::nullopt;
103 }
104};
105
109template <> struct MetadataConversions<bool, std::string> {
110public:
120 static std::optional<bool> convert(const std::string& from) {
122 std::string_view(from.data(), from.size()));
123 }
124};
125
126#pragma endregion
127
128#pragma region Conversions to integer
132template <typename TTo, typename TFrom>
134 TTo,
135 TFrom,
136 std::enable_if_t<
137 CesiumGltf::IsMetadataInteger<TTo>::value &&
138 CesiumGltf::IsMetadataInteger<TFrom>::value &&
139 !std::is_same_v<TTo, TFrom>>> {
147 static std::optional<TTo> convert(TFrom from) {
149 }
150};
151
155template <typename TTo, typename TFrom>
157 TTo,
158 TFrom,
159 std::enable_if_t<
160 CesiumGltf::IsMetadataInteger<TTo>::value &&
161 CesiumGltf::IsMetadataFloating<TFrom>::value>> {
171 static std::optional<TTo> convert(TFrom from) {
172 if (double(std::numeric_limits<TTo>::max()) < from ||
173 double(std::numeric_limits<TTo>::lowest()) > from) {
174 // Floating-point number is outside the range of this integer type.
175 return std::nullopt;
176 }
177
178 return static_cast<TTo>(from);
179 }
180};
181
185template <typename TTo>
187 TTo,
188 std::string,
189 std::enable_if_t<
190 CesiumGltf::IsMetadataInteger<TTo>::value && std::is_signed_v<TTo>>> {
200 static std::optional<TTo> convert(const std::string& from) {
201 if (from.size() == 0) {
202 // Return early. Otherwise, empty strings will be parsed as 0, which is
203 // misleading.
204 return std::nullopt;
205 }
206
207 errno = 0;
208
209 char* pLastUsed;
210 int64_t parsedValue = std::strtoll(from.c_str(), &pLastUsed, 10);
211 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
212 // Successfully parsed the entire string as an integer of this type.
214 }
215
216 errno = 0;
217
218 // Failed to parse as an integer. Maybe we can parse as a double and
219 // truncate it?
220 double parsedDouble = std::strtod(from.c_str(), &pLastUsed);
221 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
222 // Successfully parsed the entire string as a double.
223 // Convert it to an integer if we can.
224 double truncated = glm::trunc(parsedDouble);
225
226 int64_t asInteger = static_cast<int64_t>(truncated);
227 double roundTrip = static_cast<double>(asInteger);
228 if (roundTrip == truncated) {
230 }
231 }
232
233 return std::nullopt;
234 }
235}; // namespace CesiumGltf
236
240template <typename TTo>
242 TTo,
243 std::string,
244 std::enable_if_t<
245 CesiumGltf::IsMetadataInteger<TTo>::value && !std::is_signed_v<TTo>>> {
255 static std::optional<TTo> convert(const std::string& from) {
256 if (from.size() == 0) {
257 // Return early. Otherwise, empty strings will be parsed as 0, which is
258 // misleading.
259 return std::nullopt;
260 }
261
262 if (from.find('-') != std::string::npos) {
263 // The string must be manually checked for a negative sign because for
264 // std::strtoull accepts negative numbers and bitcasts them, which is not
265 // desired!
266 return std::nullopt;
267 }
268
269 errno = 0;
270
271 char* pLastUsed;
272 uint64_t parsedValue = std::strtoull(from.c_str(), &pLastUsed, 10);
273 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
274 // Successfully parsed the entire string as an integer of this type.
276 }
277
278 // Failed to parse as an integer. Maybe we can parse as a double and
279 // truncate it?
280 errno = 0;
281
282 double parsedDouble = std::strtod(from.c_str(), &pLastUsed);
283 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
284 // Successfully parsed the entire string as a double.
285 // Convert it to an integer if we can.
286 double truncated = glm::trunc(parsedDouble);
287
288 uint64_t asInteger = static_cast<uint64_t>(truncated);
289 double roundTrip = static_cast<double>(asInteger);
290 if (roundTrip == truncated) {
292 }
293 }
294
295 return std::nullopt;
296 }
297};
298
302template <typename TTo>
304 TTo,
305 std::string_view,
306 std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
316 static std::optional<TTo> convert(const std::string_view& from) {
317 if (from.size() == 0) {
318 // Return early. Otherwise, empty strings will be parsed as 0, which is
319 // misleading.
320 return std::nullopt;
321 }
322
323 // Amazingly, C++ has no* string parsing functions that work with strings
324 // that might not be null-terminated. So we have to copy to a std::string
325 // (which _is_ guaranteed to be null terminated) before parsing.
326 // * except std::from_chars, but compiler/library support for the
327 // floating-point version of that method is spotty at best.
328 return MetadataConversions<TTo, std::string>::convert(std::string(from));
329 }
330};
331
335template <typename TTo>
337 TTo,
338 bool,
339 std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
346 static std::optional<TTo> convert(bool from) { return from ? 1 : 0; }
347};
348#pragma endregion
349
350#pragma region Conversions to float
354template <> struct MetadataConversions<float, bool> {
361 static std::optional<float> convert(bool from) { return from ? 1.0f : 0.0f; }
362};
363
367template <typename TFrom>
369 float,
370 TFrom,
371 std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
378 static std::optional<float> convert(TFrom from) {
379 return static_cast<float>(from);
380 }
381};
382
386template <> struct MetadataConversions<float, double> {
395 static std::optional<float> convert(double from) {
396 if (from > std::numeric_limits<float>::max() ||
397 from < std::numeric_limits<float>::lowest()) {
398 return std::nullopt;
399 }
400 return static_cast<float>(from);
401 }
402};
403
407template <> struct MetadataConversions<float, std::string> {
416 static std::optional<float> convert(const std::string& from) {
417 if (from.size() == 0) {
418 // Return early. Otherwise, empty strings will be parsed as 0, which is
419 // misleading.
420 return std::nullopt;
421 }
422
423 errno = 0;
424
425 char* pLastUsed;
426 float parsedValue = std::strtof(from.c_str(), &pLastUsed);
427 if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
428 !std::isinf(parsedValue)) {
429 // Successfully parsed the entire string as a float.
430 return parsedValue;
431 }
432 return std::nullopt;
433 }
434};
435
439template <> struct MetadataConversions<float, std::string_view> {
448 static std::optional<float> convert(const std::string_view& from) {
449 if (from.size() == 0) {
450 // Return early. Otherwise, empty strings will be parsed as 0, which is
451 // misleading.
452 return std::nullopt;
453 }
454 // Amazingly, C++ has no* string parsing functions that work with strings
455 // that might not be null-terminated. So we have to copy to a std::string
456 // (which _is_ guaranteed to be null terminated) before parsing.
457 // * except std::from_chars, but compiler/library support for the
458 // floating-point version of that method is spotty at best.
460 std::string(from.data(), from.size()));
461 }
462};
463#pragma endregion
464
465#pragma region Conversions to double
469template <> struct MetadataConversions<double, bool> {
476 static std::optional<double> convert(bool from) { return from ? 1.0 : 0.0; }
477};
478
482template <typename TFrom>
484 double,
485 TFrom,
486 std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
493 static std::optional<double> convert(TFrom from) {
494 return static_cast<double>(from);
495 }
496};
497
501template <> struct MetadataConversions<double, float> {
507 static std::optional<double> convert(float from) {
508 return static_cast<double>(from);
509 }
510};
511
515template <> struct MetadataConversions<double, std::string> {
524 static std::optional<double> convert(const std::string& from) {
525 if (from.size() == 0) {
526 // Return early. Otherwise, empty strings will be parsed as 0, which is
527 // misleading.
528 return std::nullopt;
529 }
530
531 errno = 0;
532
533 char* pLastUsed;
534 double parsedValue = std::strtod(from.c_str(), &pLastUsed);
535 if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
536 !std::isinf(parsedValue)) {
537 // Successfully parsed the entire string as a double.
538 return parsedValue;
539 }
540 return std::nullopt;
541 }
542};
543
547template <> struct MetadataConversions<double, std::string_view> {
556 static std::optional<double> convert(const std::string_view& from) {
557 if (from.size() == 0) {
558 // Return early. Otherwise, empty strings will be parsed as 0, which is
559 // misleading.
560 return std::nullopt;
561 }
562
563 // Amazingly, C++ has no* string parsing functions that work with strings
564 // that might not be null-terminated. So we have to copy to a std::string
565 // (which _is_ guaranteed to be null terminated) before parsing.
566 // * except std::from_chars, but compiler/library support for the
567 // floating-point version of that method is spotty at best.
569 }
570};
571#pragma endregion
572
573#pragma region Conversions to string
577template <> struct MetadataConversions<std::string, bool> {
584 static std::optional<std::string> convert(bool from) {
585 return from ? "true" : "false";
586 }
587};
588
592template <typename TFrom>
594 std::string,
595 TFrom,
596 std::enable_if_t<IsMetadataScalar<TFrom>::value>> {
602 static std::optional<std::string> convert(TFrom from) {
603 return std::to_string(from);
604 }
605};
606
610template <typename TFrom>
612 std::string,
613 TFrom,
614 std::enable_if_t<
615 IsMetadataVecN<TFrom>::value || IsMetadataMatN<TFrom>::value>> {
622 static std::optional<std::string> convert(const TFrom& from) {
623 return glm::to_string(from);
624 }
625};
626
630template <> struct MetadataConversions<std::string, std::string_view> {
634 static std::optional<std::string> convert(const std::string_view& from) {
635 return std::string(from.data(), from.size());
636 }
637};
638
639#pragma endregion
640
641#pragma region Conversions to glm::vecN
645template <typename TTo>
647 TTo,
648 bool,
649 std::enable_if_t<IsMetadataVecN<TTo>::value>> {
657 static std::optional<TTo> convert(bool from) {
658 return from ? TTo(1) : TTo(0);
659 }
660};
661
665template <typename TTo, typename TFrom>
667 TTo,
668 TFrom,
669 std::enable_if_t<
670 CesiumGltf::IsMetadataVecN<TTo>::value &&
671 CesiumGltf::IsMetadataScalar<TFrom>::value>> {
683 static std::optional<TTo> convert(TFrom from) {
684 using ToValueType = typename TTo::value_type;
685
686 std::optional<ToValueType> maybeValue =
688 if (maybeValue) {
689 ToValueType value = *maybeValue;
690 return TTo(value);
691 }
692
693 return std::nullopt;
694 }
695};
696
700template <typename TTo, typename TFrom>
702 TTo,
703 TFrom,
704 std::enable_if_t<
705 CesiumGltf::IsMetadataVecN<TTo>::value &&
706 CesiumGltf::IsMetadataVecN<TFrom>::value &&
707 !std::is_same_v<TTo, TFrom>>> {
721 static std::optional<TTo> convert(TFrom from) {
722 TTo result = TTo(0);
723
724 constexpr glm::length_t validLength =
725 glm::min(TTo::length(), TFrom::length());
726
727 using ToValueType = typename TTo::value_type;
728 using FromValueType = typename TFrom::value_type;
729
730 for (glm::length_t i = 0; i < validLength; i++) {
731 auto maybeValue =
733 if (!maybeValue) {
734 return std::nullopt;
735 }
736
737 result[i] = *maybeValue;
738 }
739
740 return result;
741 }
742};
743#pragma endregion
744
745#pragma region Conversions to glm::matN
749template <typename TTo>
751 TTo,
752 bool,
753 std::enable_if_t<IsMetadataMatN<TTo>::value>> {
761 static std::optional<TTo> convert(bool from) {
762 return from ? TTo(1) : TTo(0);
763 }
764};
765
769template <typename TTo, typename TFrom>
771 TTo,
772 TFrom,
773 std::enable_if_t<
774 CesiumGltf::IsMetadataMatN<TTo>::value &&
775 CesiumGltf::IsMetadataScalar<TFrom>::value>> {
787 static std::optional<TTo> convert(TFrom from) {
788 using ToValueType = typename TTo::value_type;
789
790 std::optional<ToValueType> maybeValue =
792 if (!maybeValue) {
793 return std::nullopt;
794 }
795
796 ToValueType value = *maybeValue;
797 return TTo(value);
798 }
799};
800
804template <typename TTo, typename TFrom>
806 TTo,
807 TFrom,
808 std::enable_if_t<
809 CesiumGltf::IsMetadataMatN<TTo>::value &&
810 CesiumGltf::IsMetadataMatN<TFrom>::value &&
811 !std::is_same_v<TTo, TFrom>>> {
825 static std::optional<TTo> convert(TFrom from) {
826 TTo result = TTo(0);
827
828 constexpr glm::length_t validLength =
829 glm::min(TTo::length(), TFrom::length());
830
831 using ToValueType = typename TTo::value_type;
832 using FromValueType = typename TFrom::value_type;
833
834 for (glm::length_t c = 0; c < validLength; c++) {
835 for (glm::length_t r = 0; r < validLength; r++) {
836 auto maybeValue =
838 from[c][r]);
839 if (!maybeValue) {
840 return std::nullopt;
841 }
842
843 result[c][r] = *maybeValue;
844 }
845 }
846
847 return result;
848 }
849};
850#pragma endregion
851
852} // namespace CesiumGltf
Classes for working with glTF models.
std::optional< TTo > losslessNarrow(TFrom from) noexcept
Attempts a narrowing conversion of TFrom into TTo without losing information. If a lossless conversio...
STL namespace.
static std::optional< TTo > convert(TFrom from)
Converts a value of the given integer to another integer type. If the integer cannot be losslessly co...
static std::optional< TTo > convert(TFrom from)
Converts a floating-point value to an integer type. This truncates the floating-point value,...
static std::optional< TTo > convert(TFrom from)
Converts a scalar to a vecN. The returned vector is initialized with the value in all of its componen...
static std::optional< TTo > convert(bool from)
Converts a boolean to an integer. This returns 1 for true, 0 for false.
static std::optional< TTo > convert(bool from)
Converts a boolean to a matN. The boolean is converted to an integer value of 1 for true or 0 for fal...
static std::optional< TTo > convert(bool from)
Converts a boolean to a vecN. The boolean is converted to an integer value of 1 for true or 0 for fal...
static std::optional< TTo > convert(const std::string &from)
Converts the contents of a std::string to a signed integer. This assumes that the entire std::string ...
static std::optional< TTo > convert(const std::string &from)
Converts the contents of a std::string to an unsigned integer. This assumes that the entire std::stri...
static std::optional< TTo > convert(const std::string_view &from)
Converts the contents of a std::string_view to an integer. This assumes that the entire std::string_v...
static std::optional< T > convert(T from)
Converts an instance of T to an instance of T, always returning the same value that was passed in.
static std::optional< bool > convert(TFrom from)
Converts a scalar to a boolean. Zero is converted to false, while nonzero values are converted to tru...
static std::optional< bool > convert(const std::string &from)
Converts the contents of a std::string to a boolean.
static std::optional< bool > convert(const std::string_view &from)
Converts the contents of a std::string_view to a boolean.
static std::optional< double > convert(TFrom from)
Converts any integer type to a double. The value may lose precision during conversion.
static std::optional< double > convert(bool from)
Converts a boolean to a double. This returns 1.0 for true, 0.0 for false.
static std::optional< double > convert(float from)
Converts from a float to a double.
static std::optional< double > convert(const std::string &from)
static std::optional< double > convert(const std::string_view &from)
static std::optional< float > convert(TFrom from)
Converts an integer to a float. The value may lose precision during conversion.
static std::optional< float > convert(bool from)
Converts a boolean to a float. This returns 1.0f for true, 0.0f for false.
static std::optional< float > convert(double from)
Converts a double to a float. The value may lose precision during conversion.
static std::optional< float > convert(const std::string &from)
Converts a std::string to a float. This assumes that the entire std::string represents the number,...
static std::optional< float > convert(const std::string_view &from)
Converts a std::string_view to a float. This assumes that the entire std::string_view represents the ...
static std::optional< std::string > convert(const TFrom &from)
Converts a glm::vecN or glm::matN to a std::string. This uses the format that glm::to_string() output...
static std::optional< std::string > convert(TFrom from)
Converts a scalar to a std::string.
static std::optional< std::string > convert(bool from)
Converts a boolean to a std::string. Returns "true" for true and "false" for false.
static std::optional< std::string > convert(const std::string_view &from)
Converts from a std::string_view to a std::string.
Default conversion between two types. No actual conversion is defined. This returns std::nullopt to i...
static std::optional< TTo > convert(TFrom)
Converts between TFrom and TTo where no actual conversion is defined, returning std::nullopt.