cesium-native  0.41.0
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 
23 namespace CesiumGltf {
28 template <typename TTo, typename TFrom, typename Enable = void>
30  static std::optional<TTo> convert(TFrom /*from*/) { return std::nullopt; }
31 };
32 
36 template <typename T> struct MetadataConversions<T, T> {
37  static std::optional<T> convert(T from) { return from; }
38 };
39 
40 #pragma region Conversions to boolean
44 template <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 
63 template <> struct MetadataConversions<bool, std::string_view> {
64 private:
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 
79 public:
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 
109 template <> struct MetadataConversions<bool, std::string> {
110 public:
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
132 template <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) {
148  return CesiumUtility::losslessNarrow<TTo, TFrom>(from);
149  }
150 };
151 
155 template <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 
185 template <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.
213  return CesiumUtility::losslessNarrow<TTo, int64_t>(parsedValue);
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) {
229  return CesiumUtility::losslessNarrow<TTo, int64_t>(asInteger);
230  }
231  }
232 
233  return std::nullopt;
234  }
235 }; // namespace CesiumGltf
236 
240 template <typename TTo>
242  TTo,
243  std::string,
244  std::enable_if_t<
245  CesiumGltf::IsMetadataInteger<TTo>::value && !std::is_signed_v<TTo>>> {
256  static std::optional<TTo> convert(const std::string& from) {
257  if (from.size() == 0) {
258  // Return early. Otherwise, empty strings will be parsed as 0, which is
259  // misleading.
260  return std::nullopt;
261  }
262 
263  if (from.find('-') != std::string::npos) {
264  // The string must be manually checked for a negative sign because for
265  // std::strtoull accepts negative numbers and bitcasts them, which is not
266  // desired!
267  return std::nullopt;
268  }
269 
270  errno = 0;
271 
272  char* pLastUsed;
273  uint64_t parsedValue = std::strtoull(from.c_str(), &pLastUsed, 10);
274  if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
275  // Successfully parsed the entire string as an integer of this type.
276  return CesiumUtility::losslessNarrow<TTo, uint64_t>(parsedValue);
277  }
278 
279  // Failed to parse as an integer. Maybe we can parse as a double and
280  // truncate it?
281  errno = 0;
282 
283  double parsedDouble = std::strtod(from.c_str(), &pLastUsed);
284  if (errno == 0 && pLastUsed == from.c_str() + from.size()) {
285  // Successfully parsed the entire string as a double.
286  // Convert it to an integer if we can.
287  double truncated = glm::trunc(parsedDouble);
288 
289  uint64_t asInteger = static_cast<uint64_t>(truncated);
290  double roundTrip = static_cast<double>(asInteger);
291  if (roundTrip == truncated) {
292  return CesiumUtility::losslessNarrow<TTo, uint64_t>(asInteger);
293  }
294  }
295 
296  return std::nullopt;
297  }
298 };
299 
303 template <typename TTo>
305  TTo,
306  std::string_view,
307  std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
317  static std::optional<TTo> convert(const std::string_view& from) {
318  if (from.size() == 0) {
319  // Return early. Otherwise, empty strings will be parsed as 0, which is
320  // misleading.
321  return std::nullopt;
322  }
323 
324  // Amazingly, C++ has no* string parsing functions that work with strings
325  // that might not be null-terminated. So we have to copy to a std::string
326  // (which _is_ guaranteed to be null terminated) before parsing.
327  // * except std::from_chars, but compiler/library support for the
328  // floating-point version of that method is spotty at best.
329  return MetadataConversions<TTo, std::string>::convert(std::string(from));
330  }
331 };
332 
336 template <typename TTo>
338  TTo,
339  bool,
340  std::enable_if_t<CesiumGltf::IsMetadataInteger<TTo>::value>> {
347  static std::optional<TTo> convert(bool from) { return from ? 1 : 0; }
348 };
349 #pragma endregion
350 
351 #pragma region Conversions to float
355 template <> struct MetadataConversions<float, bool> {
362  static std::optional<float> convert(bool from) { return from ? 1.0f : 0.0f; }
363 };
364 
368 template <typename TFrom>
370  float,
371  TFrom,
372  std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
379  static std::optional<float> convert(TFrom from) {
380  return static_cast<float>(from);
381  }
382 };
383 
387 template <> struct MetadataConversions<float, double> {
396  static std::optional<float> convert(double from) {
397  if (from > std::numeric_limits<float>::max() ||
398  from < std::numeric_limits<float>::lowest()) {
399  return std::nullopt;
400  }
401  return static_cast<float>(from);
402  }
403 };
404 
408 template <> struct MetadataConversions<float, std::string> {
417  static std::optional<float> convert(const std::string& from) {
418  if (from.size() == 0) {
419  // Return early. Otherwise, empty strings will be parsed as 0, which is
420  // misleading.
421  return std::nullopt;
422  }
423 
424  errno = 0;
425 
426  char* pLastUsed;
427  float parsedValue = std::strtof(from.c_str(), &pLastUsed);
428  if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
429  !std::isinf(parsedValue)) {
430  // Successfully parsed the entire string as a float.
431  return parsedValue;
432  }
433  return std::nullopt;
434  }
435 };
436 
440 template <> struct MetadataConversions<float, std::string_view> {
449  static std::optional<float> convert(const std::string_view& from) {
450  if (from.size() == 0) {
451  // Return early. Otherwise, empty strings will be parsed as 0, which is
452  // misleading.
453  return std::nullopt;
454  }
455  // Amazingly, C++ has no* string parsing functions that work with strings
456  // that might not be null-terminated. So we have to copy to a std::string
457  // (which _is_ guaranteed to be null terminated) before parsing.
458  // * except std::from_chars, but compiler/library support for the
459  // floating-point version of that method is spotty at best.
461  std::string(from.data(), from.size()));
462  }
463 };
464 #pragma endregion
465 
466 #pragma region Conversions to double
470 template <> struct MetadataConversions<double, bool> {
477  static std::optional<double> convert(bool from) { return from ? 1.0 : 0.0; }
478 };
479 
483 template <typename TFrom>
485  double,
486  TFrom,
487  std::enable_if_t<CesiumGltf::IsMetadataInteger<TFrom>::value>> {
494  static std::optional<double> convert(TFrom from) {
495  return static_cast<double>(from);
496  }
497 };
498 
502 template <> struct MetadataConversions<double, float> {
508  static std::optional<double> convert(float from) {
509  return static_cast<double>(from);
510  }
511 };
512 
516 template <> struct MetadataConversions<double, std::string> {
525  static std::optional<double> convert(const std::string& from) {
526  if (from.size() == 0) {
527  // Return early. Otherwise, empty strings will be parsed as 0, which is
528  // misleading.
529  return std::nullopt;
530  }
531 
532  errno = 0;
533 
534  char* pLastUsed;
535  double parsedValue = std::strtod(from.c_str(), &pLastUsed);
536  if (errno == 0 && pLastUsed == from.c_str() + from.size() &&
537  !std::isinf(parsedValue)) {
538  // Successfully parsed the entire string as a double.
539  return parsedValue;
540  }
541  return std::nullopt;
542  }
543 };
544 
548 template <> struct MetadataConversions<double, std::string_view> {
557  static std::optional<double> convert(const std::string_view& from) {
558  if (from.size() == 0) {
559  // Return early. Otherwise, empty strings will be parsed as 0, which is
560  // misleading.
561  return std::nullopt;
562  }
563 
564  // Amazingly, C++ has no* string parsing functions that work with strings
565  // that might not be null-terminated. So we have to copy to a std::string
566  // (which _is_ guaranteed to be null terminated) before parsing.
567  // * except std::from_chars, but compiler/library support for the
568  // floating-point version of that method is spotty at best.
569  return MetadataConversions<double, std::string>::convert(std::string(from));
570  }
571 };
572 #pragma endregion
573 
574 #pragma region Conversions to string
578 template <> struct MetadataConversions<std::string, bool> {
585  static std::optional<std::string> convert(bool from) {
586  return from ? "true" : "false";
587  }
588 };
589 
593 template <typename TFrom>
595  std::string,
596  TFrom,
597  std::enable_if_t<IsMetadataScalar<TFrom>::value>> {
604  static std::optional<std::string> convert(TFrom from) {
605  return std::to_string(from);
606  }
607 };
608 
612 template <typename TFrom>
614  std::string,
615  TFrom,
616  std::enable_if_t<
617  IsMetadataVecN<TFrom>::value || IsMetadataMatN<TFrom>::value>> {
624  static std::optional<std::string> convert(const TFrom& from) {
625  return glm::to_string(from);
626  }
627 };
628 
632 template <> struct MetadataConversions<std::string, std::string_view> {
636  static std::optional<std::string> convert(const std::string_view& from) {
637  return std::string(from.data(), from.size());
638  }
639 };
640 
641 #pragma endregion
642 
643 #pragma region Conversions to glm::vecN
647 template <typename TTo>
649  TTo,
650  bool,
651  std::enable_if_t<IsMetadataVecN<TTo>::value>> {
659  static std::optional<TTo> convert(bool from) {
660  return from ? TTo(1) : TTo(0);
661  }
662 };
663 
667 template <typename TTo, typename TFrom>
669  TTo,
670  TFrom,
671  std::enable_if_t<
672  CesiumGltf::IsMetadataVecN<TTo>::value &&
673  CesiumGltf::IsMetadataScalar<TFrom>::value>> {
685  static std::optional<TTo> convert(TFrom from) {
686  using ToValueType = typename TTo::value_type;
687 
688  std::optional<ToValueType> maybeValue =
690  if (maybeValue) {
691  ToValueType value = *maybeValue;
692  return TTo(value);
693  }
694 
695  return std::nullopt;
696  }
697 };
698 
702 template <typename TTo, typename TFrom>
704  TTo,
705  TFrom,
706  std::enable_if_t<
707  CesiumGltf::IsMetadataVecN<TTo>::value &&
708  CesiumGltf::IsMetadataVecN<TFrom>::value &&
709  !std::is_same_v<TTo, TFrom>>> {
723  static std::optional<TTo> convert(TFrom from) {
724  TTo result = TTo(0);
725 
726  constexpr glm::length_t validLength =
727  glm::min(TTo::length(), TFrom::length());
728 
729  using ToValueType = typename TTo::value_type;
730  using FromValueType = typename TFrom::value_type;
731 
732  for (glm::length_t i = 0; i < validLength; i++) {
733  auto maybeValue =
735  if (!maybeValue) {
736  return std::nullopt;
737  }
738 
739  result[i] = *maybeValue;
740  }
741 
742  return result;
743  }
744 };
745 #pragma endregion
746 
747 #pragma region Conversions to glm::matN
751 template <typename TTo>
753  TTo,
754  bool,
755  std::enable_if_t<IsMetadataMatN<TTo>::value>> {
763  static std::optional<TTo> convert(bool from) {
764  return from ? TTo(1) : TTo(0);
765  }
766 };
767 
771 template <typename TTo, typename TFrom>
773  TTo,
774  TFrom,
775  std::enable_if_t<
776  CesiumGltf::IsMetadataMatN<TTo>::value &&
777  CesiumGltf::IsMetadataScalar<TFrom>::value>> {
789  static std::optional<TTo> convert(TFrom from) {
790  using ToValueType = typename TTo::value_type;
791 
792  std::optional<ToValueType> maybeValue =
794  if (!maybeValue) {
795  return std::nullopt;
796  }
797 
798  ToValueType value = *maybeValue;
799  return TTo(value);
800  }
801 };
802 
806 template <typename TTo, typename TFrom>
808  TTo,
809  TFrom,
810  std::enable_if_t<
811  CesiumGltf::IsMetadataMatN<TTo>::value &&
812  CesiumGltf::IsMetadataMatN<TFrom>::value &&
813  !std::is_same_v<TTo, TFrom>>> {
827  static std::optional<TTo> convert(TFrom from) {
828  TTo result = TTo(0);
829 
830  constexpr glm::length_t validLength =
831  glm::min(TTo::length(), TFrom::length());
832 
833  using ToValueType = typename TTo::value_type;
834  using FromValueType = typename TFrom::value_type;
835 
836  for (glm::length_t c = 0; c < validLength; c++) {
837  for (glm::length_t r = 0; r < validLength; r++) {
838  auto maybeValue =
840  from[c][r]);
841  if (!maybeValue) {
842  return std::nullopt;
843  }
844 
845  result[c][r] = *maybeValue;
846  }
847  }
848 
849  return result;
850  }
851 };
852 #pragma endregion
853 
854 } // namespace CesiumGltf
855 
856 #ifdef GLM_ENABLE_EXPERIMENTAL_defined_locally
857 #undef GLM_ENABLE_EXPERIMENTAL
858 #undef GLM_ENABLE_EXPERIMENTAL_defined_locally
859 #endif
Classes for working with glTF models.
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< 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...