cesium-native 0.43.0
Loading...
Searching...
No Matches
MetadataConversions.h
1#pragma once
2
3#include "PropertyTypeTraits.h"
4
5#include <CesiumUtility/JsonValue.h>
6
7#include <glm/common.hpp>
8
9#include <cerrno>
10#include <cstdint>
11#include <optional>
12#include <string>
13#include <string_view>
14
15#ifndef GLM_ENABLE_EXPERIMENTAL
16// If we define GLM_ENABLE_EXPERIMENTAL here, we undefine it at the end of this
17// header file.
18#define GLM_ENABLE_EXPERIMENTAL
19#define GLM_ENABLE_EXPERIMENTAL_defined_locally
20#endif
21#include <glm/gtx/string_cast.hpp>
22
23namespace CesiumGltf {
28template <typename TTo, typename TFrom, typename Enable = void>
34 static std::optional<TTo> convert(TFrom /*from*/) { return std::nullopt; }
35};
36
40template <typename T> struct MetadataConversions<T, T> {
45 static std::optional<T> convert(T from) { return from; }
46};
47
48#pragma region Conversions to boolean
52template <typename TFrom>
54 bool,
55 TFrom,
56 std::enable_if_t<CesiumGltf::IsMetadataScalar<TFrom>::value>> {
63 static std::optional<bool> convert(TFrom from) {
64 return from != static_cast<TFrom>(0);
65 }
66};
67
71template <> struct MetadataConversions<bool, std::string_view> {
72private:
73 static bool
74 isEqualCaseInsensitive(const std::string_view& a, const std::string_view& b) {
75 if (a.size() != b.size()) {
76 return false;
77 }
78
79 for (size_t i = 0; i < a.size(); i++) {
80 if (std::tolower(a[i]) != std::tolower(b[i])) {
81 return false;
82 }
83 }
84 return true;
85 }
86
87public:
97 static std::optional<bool> convert(const std::string_view& from) {
98 if (isEqualCaseInsensitive(from, "1") ||
99 isEqualCaseInsensitive(from, "true") ||
100 isEqualCaseInsensitive(from, "yes")) {
101 return true;
102 }
103
104 if (isEqualCaseInsensitive(from, "0") ||
105 isEqualCaseInsensitive(from, "false") ||
106 isEqualCaseInsensitive(from, "no")) {
107 return false;
108 }
109
110 return std::nullopt;
111 }
112};
113
117template <> struct MetadataConversions<bool, std::string> {
118public:
128 static std::optional<bool> convert(const std::string& from) {
130 std::string_view(from.data(), from.size()));
131 }
132};
133
134#pragma endregion
135
136#pragma region Conversions to integer
140template <typename TTo, typename TFrom>
142 TTo,
143 TFrom,
144 std::enable_if_t<
145 CesiumGltf::IsMetadataInteger<TTo>::value &&
146 CesiumGltf::IsMetadataInteger<TFrom>::value &&
147 !std::is_same_v<TTo, TFrom>>> {
155 static std::optional<TTo> convert(TFrom from) {
157 }
158};
159
163template <typename TTo, typename TFrom>
165 TTo,
166 TFrom,
167 std::enable_if_t<
168 CesiumGltf::IsMetadataInteger<TTo>::value &&
169 CesiumGltf::IsMetadataFloating<TFrom>::value>> {
179 static std::optional<TTo> convert(TFrom from) {
180 if (double(std::numeric_limits<TTo>::max()) < from ||
181 double(std::numeric_limits<TTo>::lowest()) > from) {
182 // Floating-point number is outside the range of this integer type.
183 return std::nullopt;
184 }
185
186 return static_cast<TTo>(from);
187 }
188};
189
193template <typename TTo>
195 TTo,
196 std::string,
197 std::enable_if_t<
198 CesiumGltf::IsMetadataInteger<TTo>::value && std::is_signed_v<TTo>>> {
208 static std::optional<TTo> convert(const std::string& from) {
209 if (from.size() == 0) {
210 // Return early. Otherwise, empty strings will be parsed as 0, which is
211 // misleading.
212 return std::nullopt;
213 }
214
215 errno = 0;
216
217 char* pLastUsed;
218 int64_t parsedValue = std::strtoll(from.c_str(), &pLastUsed, 10);
219 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
220 // Successfully parsed the entire string as an integer of this type.
222 }
223
224 errno = 0;
225
226 // Failed to parse as an integer. Maybe we can parse as a double and
227 // truncate it?
228 double parsedDouble = std::strtod(from.c_str(), &pLastUsed);
229 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
230 // Successfully parsed the entire string as a double.
231 // Convert it to an integer if we can.
232 double truncated = glm::trunc(parsedDouble);
233
234 int64_t asInteger = static_cast<int64_t>(truncated);
235 double roundTrip = static_cast<double>(asInteger);
236 if (roundTrip == truncated) {
238 }
239 }
240
241 return std::nullopt;
242 }
243}; // namespace CesiumGltf
244
248template <typename TTo>
250 TTo,
251 std::string,
252 std::enable_if_t<
253 CesiumGltf::IsMetadataInteger<TTo>::value && !std::is_signed_v<TTo>>> {
263 static std::optional<TTo> convert(const std::string& from) {
264 if (from.size() == 0) {
265 // Return early. Otherwise, empty strings will be parsed as 0, which is
266 // misleading.
267 return std::nullopt;
268 }
269
270 if (from.find('-') != std::string::npos) {
271 // The string must be manually checked for a negative sign because for
272 // std::strtoull accepts negative numbers and bitcasts them, which is not
273 // desired!
274 return std::nullopt;
275 }
276
277 errno = 0;
278
279 char* pLastUsed;
280 uint64_t parsedValue = std::strtoull(from.c_str(), &pLastUsed, 10);
281 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
282 // Successfully parsed the entire string as an integer of this type.
284 }
285
286 // Failed to parse as an integer. Maybe we can parse as a double and
287 // truncate it?
288 errno = 0;
289
290 double parsedDouble = std::strtod(from.c_str(), &pLastUsed);
291 if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
292 // Successfully parsed the entire string as a double.
293 // Convert it to an integer if we can.
294 double truncated = glm::trunc(parsedDouble);
295
296 uint64_t asInteger = static_cast<uint64_t>(truncated);
297 double roundTrip = static_cast<double>(asInteger);
298 if (roundTrip == truncated) {
300 }
301 }
302
303 return std::nullopt;
304 }
305};
306
310template <typename TTo>
312 TTo,
313 std::string_view,
314 std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
324 static std::optional<TTo> convert(const std::string_view& from) {
325 if (from.size() == 0) {
326 // Return early. Otherwise, empty strings will be parsed as 0, which is
327 // misleading.
328 return std::nullopt;
329 }
330
331 // Amazingly, C++ has no* string parsing functions that work with strings
332 // that might not be null-terminated. So we have to copy to a std::string
333 // (which _is_ guaranteed to be null terminated) before parsing.
334 // * except std::from_chars, but compiler/library support for the
335 // floating-point version of that method is spotty at best.
336 return MetadataConversions<TTo, std::string>::convert(std::string(from));
337 }
338};
339
343template <typename TTo>
345 TTo,
346 bool,
347 std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
354 static std::optional<TTo> convert(bool from) { return from ? 1 : 0; }
355};
356#pragma endregion
357
358#pragma region Conversions to float
362template <> struct MetadataConversions<float, bool> {
369 static std::optional<float> convert(bool from) { return from ? 1.0f : 0.0f; }
370};
371
375template <typename TFrom>
377 float,
378 TFrom,
379 std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
386 static std::optional<float> convert(TFrom from) {
387 return static_cast<float>(from);
388 }
389};
390
394template <> struct MetadataConversions<float, double> {
403 static std::optional<float> convert(double from) {
404 if (from > std::numeric_limits<float>::max() ||
405 from < std::numeric_limits<float>::lowest()) {
406 return std::nullopt;
407 }
408 return static_cast<float>(from);
409 }
410};
411
415template <> struct MetadataConversions<float, std::string> {
424 static std::optional<float> convert(const std::string& from) {
425 if (from.size() == 0) {
426 // Return early. Otherwise, empty strings will be parsed as 0, which is
427 // misleading.
428 return std::nullopt;
429 }
430
431 errno = 0;
432
433 char* pLastUsed;
434 float parsedValue = std::strtof(from.c_str(), &pLastUsed);
435 if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
436 !std::isinf(parsedValue)) {
437 // Successfully parsed the entire string as a float.
438 return parsedValue;
439 }
440 return std::nullopt;
441 }
442};
443
447template <> struct MetadataConversions<float, std::string_view> {
456 static std::optional<float> convert(const std::string_view& from) {
457 if (from.size() == 0) {
458 // Return early. Otherwise, empty strings will be parsed as 0, which is
459 // misleading.
460 return std::nullopt;
461 }
462 // Amazingly, C++ has no* string parsing functions that work with strings
463 // that might not be null-terminated. So we have to copy to a std::string
464 // (which _is_ guaranteed to be null terminated) before parsing.
465 // * except std::from_chars, but compiler/library support for the
466 // floating-point version of that method is spotty at best.
468 std::string(from.data(), from.size()));
469 }
470};
471#pragma endregion
472
473#pragma region Conversions to double
477template <> struct MetadataConversions<double, bool> {
484 static std::optional<double> convert(bool from) { return from ? 1.0 : 0.0; }
485};
486
490template <typename TFrom>
492 double,
493 TFrom,
494 std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
501 static std::optional<double> convert(TFrom from) {
502 return static_cast<double>(from);
503 }
504};
505
509template <> struct MetadataConversions<double, float> {
515 static std::optional<double> convert(float from) {
516 return static_cast<double>(from);
517 }
518};
519
523template <> struct MetadataConversions<double, std::string> {
532 static std::optional<double> convert(const std::string& from) {
533 if (from.size() == 0) {
534 // Return early. Otherwise, empty strings will be parsed as 0, which is
535 // misleading.
536 return std::nullopt;
537 }
538
539 errno = 0;
540
541 char* pLastUsed;
542 double parsedValue = std::strtod(from.c_str(), &pLastUsed);
543 if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
544 !std::isinf(parsedValue)) {
545 // Successfully parsed the entire string as a double.
546 return parsedValue;
547 }
548 return std::nullopt;
549 }
550};
551
555template <> struct MetadataConversions<double, std::string_view> {
564 static std::optional<double> convert(const std::string_view& from) {
565 if (from.size() == 0) {
566 // Return early. Otherwise, empty strings will be parsed as 0, which is
567 // misleading.
568 return std::nullopt;
569 }
570
571 // Amazingly, C++ has no* string parsing functions that work with strings
572 // that might not be null-terminated. So we have to copy to a std::string
573 // (which _is_ guaranteed to be null terminated) before parsing.
574 // * except std::from_chars, but compiler/library support for the
575 // floating-point version of that method is spotty at best.
577 }
578};
579#pragma endregion
580
581#pragma region Conversions to string
585template <> struct MetadataConversions<std::string, bool> {
592 static std::optional<std::string> convert(bool from) {
593 return from ? "true" : "false";
594 }
595};
596
600template <typename TFrom>
602 std::string,
603 TFrom,
604 std::enable_if_t<IsMetadataScalar<TFrom>::value>> {
610 static std::optional<std::string> convert(TFrom from) {
611 return std::to_string(from);
612 }
613};
614
618template <typename TFrom>
620 std::string,
621 TFrom,
622 std::enable_if_t<
623 IsMetadataVecN<TFrom>::value || IsMetadataMatN<TFrom>::value>> {
630 static std::optional<std::string> convert(const TFrom& from) {
631 return glm::to_string(from);
632 }
633};
634
638template <> struct MetadataConversions<std::string, std::string_view> {
642 static std::optional<std::string> convert(const std::string_view& from) {
643 return std::string(from.data(), from.size());
644 }
645};
646
647#pragma endregion
648
649#pragma region Conversions to glm::vecN
653template <typename TTo>
655 TTo,
656 bool,
657 std::enable_if_t<IsMetadataVecN<TTo>::value>> {
665 static std::optional<TTo> convert(bool from) {
666 return from ? TTo(1) : TTo(0);
667 }
668};
669
673template <typename TTo, typename TFrom>
675 TTo,
676 TFrom,
677 std::enable_if_t<
678 CesiumGltf::IsMetadataVecN<TTo>::value &&
679 CesiumGltf::IsMetadataScalar<TFrom>::value>> {
691 static std::optional<TTo> convert(TFrom from) {
692 using ToValueType = typename TTo::value_type;
693
694 std::optional<ToValueType> maybeValue =
696 if (maybeValue) {
697 ToValueType value = *maybeValue;
698 return TTo(value);
699 }
700
701 return std::nullopt;
702 }
703};
704
708template <typename TTo, typename TFrom>
710 TTo,
711 TFrom,
712 std::enable_if_t<
713 CesiumGltf::IsMetadataVecN<TTo>::value &&
714 CesiumGltf::IsMetadataVecN<TFrom>::value &&
715 !std::is_same_v<TTo, TFrom>>> {
729 static std::optional<TTo> convert(TFrom from) {
730 TTo result = TTo(0);
731
732 constexpr glm::length_t validLength =
733 glm::min(TTo::length(), TFrom::length());
734
735 using ToValueType = typename TTo::value_type;
736 using FromValueType = typename TFrom::value_type;
737
738 for (glm::length_t i = 0; i < validLength; i++) {
739 auto maybeValue =
741 if (!maybeValue) {
742 return std::nullopt;
743 }
744
745 result[i] = *maybeValue;
746 }
747
748 return result;
749 }
750};
751#pragma endregion
752
753#pragma region Conversions to glm::matN
757template <typename TTo>
759 TTo,
760 bool,
761 std::enable_if_t<IsMetadataMatN<TTo>::value>> {
769 static std::optional<TTo> convert(bool from) {
770 return from ? TTo(1) : TTo(0);
771 }
772};
773
777template <typename TTo, typename TFrom>
779 TTo,
780 TFrom,
781 std::enable_if_t<
782 CesiumGltf::IsMetadataMatN<TTo>::value &&
783 CesiumGltf::IsMetadataScalar<TFrom>::value>> {
795 static std::optional<TTo> convert(TFrom from) {
796 using ToValueType = typename TTo::value_type;
797
798 std::optional<ToValueType> maybeValue =
800 if (!maybeValue) {
801 return std::nullopt;
802 }
803
804 ToValueType value = *maybeValue;
805 return TTo(value);
806 }
807};
808
812template <typename TTo, typename TFrom>
814 TTo,
815 TFrom,
816 std::enable_if_t<
817 CesiumGltf::IsMetadataMatN<TTo>::value &&
818 CesiumGltf::IsMetadataMatN<TFrom>::value &&
819 !std::is_same_v<TTo, TFrom>>> {
833 static std::optional<TTo> convert(TFrom from) {
834 TTo result = TTo(0);
835
836 constexpr glm::length_t validLength =
837 glm::min(TTo::length(), TFrom::length());
838
839 using ToValueType = typename TTo::value_type;
840 using FromValueType = typename TFrom::value_type;
841
842 for (glm::length_t c = 0; c < validLength; c++) {
843 for (glm::length_t r = 0; r < validLength; r++) {
844 auto maybeValue =
846 from[c][r]);
847 if (!maybeValue) {
848 return std::nullopt;
849 }
850
851 result[c][r] = *maybeValue;
852 }
853 }
854
855 return result;
856 }
857};
858#pragma endregion
859
860} // namespace CesiumGltf
861
862#ifdef GLM_ENABLE_EXPERIMENTAL_defined_locally
863#undef GLM_ENABLE_EXPERIMENTAL
864#undef GLM_ENABLE_EXPERIMENTAL_defined_locally
865#endif
Classes for working with glTF models.
constexpr std::optional< T > losslessNarrow(U u) noexcept
Attempts a narrowing conversion of U into T without losing information. If a lossless conversion can'...
Definition JsonValue.h:29
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.